PHP Classes

File: CSVIterator.php

Recommend this page to a friend!
  Classes of Sam S   CSV Iterator   CSVIterator.php   Download  
File: CSVIterator.php
Role: Class source
Content type: text/plain
Description: The class file
Class: CSV Iterator
Manipulate data in CSV files as if they are arrays
Author: By
Last change: fix allow_calltime_pass_by_refererence errors
added CSVIterator::save method to enable copying of data to new file
added CSVIteratorRow::toArray
fixed CSVIterator::offsetSet
fixed CSVIterator::rewind to set CSVIterator::currentRow
set CSVIteratorRow::$saveChanges to true by default
Date: 14 years ago
Size: 25,546 bytes
 

Contents

Class file image Download
<?php /** * This package contains two classes for iterating over a csv file * as though it were a native php array * * @author Sam Shull <sam.shull@jhspecialty.com> * @version 1.0 * * @copyright Copyright (c) 2009 Sam Shull <sam.shull@jhspeicalty.com> * @license <http://www.opensource.org/licenses/mit-license.html> * * 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. * * * Changes: * 07/08/2009 - fix allow_calltime_pass_by_refererence errors * added CSVIterator::save method to enable copying of data to new file * added CSVIteratorRow::toArray * fixed CSVIterator::offsetSet * fixed CSVIterator::rewind to set CSVIterator::currentRow * set CSVIteratorRow::$saveChanges to true by default * */ require_once 'streamfunctions.php'; /** * * * <code> * $csv = new CSVIterator('code.csv'); * * $i = 0; * * foreach ($csv as $key => $row) * { * print "{$key}: {$i}: {$row[$i]}\n"; * $i += $i < count($row)-1 ? 1 : -$i; * } * </code> * */ class CSVIterator implements ArrayAccess, Iterator, Countable { /***************************************************************************************************************** * * properties * /*****************************************************************************************************************/ /** * * * @var resource */ protected $pointer; /** * * * @var string */ protected $filename; /** * * * @var array */ protected $lines; /** * * * @var array */ protected $currentRow; /** * * * @var integer */ public $length = 0; /** * * * @var string */ public $delimiter = ","; /** * * * @var string */ public $enclosure = '"'; /** * * * @var string */ public $escape = '\\'; /** * * * @var integer */ public $maxCount = 0; /** * A private function for indexing the contents * of the CSV file. Random line fetches perform faster. * */ private function setup () { rewind($this->pointer); //file should be locked during this time flock($this->pointer, LOCK_SH); $this->lines = array(); $this->maxCount = 0; while (!feof($this->pointer)) { $current = ftell($this->pointer); $arr = version_compare(PHP_VERSION, "5.3", ">=") ? fgetcsv($this->pointer, $this->length, $this->delimiter, $this->enclosure, $this->escape) : fgetcsv($this->pointer, $this->length, $this->delimiter, $this->enclosure); //end of csv is before eof if (false === $arr) { break; } $count = count($arr); $this->lines[] = $current; //keep track of the if ($count > $this->maxCount) { $this->maxCount = $count; } } rewind($this->pointer); //unlock file flock($this->pointer, LOCK_UN); $this->next(); return; } /** * * * */ public function normalize () { rewind($this->pointer); //lock for writing flock($this->pointer, LOCK_EX); while (!feof($this->pointer)) { $current = ftell($this->pointer); $arr = version_compare(PHP_VERSION, "5.3", ">=") ? fgetcsv($this->pointer, $this->length, $this->delimiter, $this->enclosure, $this->escape) : fgetcsv($this->pointer, $this->length, $this->delimiter, $this->enclosure);; $count = count($arr); if ($count < $this->maxCount) { fseek($this->pointer, $current, SEEK_SET); fremovecsv($this->pointer, $this->length, $this->delimiter, $this->enclosure); finsertcsv($this->pointer, array_pad($arr, $this->maxCount, ""), $this->delimiter, $this->enclosure); } } flock($this->pointer, LOCK_UN); $this->setup(); $this->next(); return; } /** * Save the Iterator to a new file * * @param string $filename * @return integer | boolean */ public function save ($filename) { if ($f = fopen($filename, 'w')) { $tell = ftell($this->pointer); $this->rewind(); foreach ($this as $row) { fwrite($f, strval($row) . "\n"); } fclose($f); fseek($this->pointer, $tell, SEEK_SET); return filesize($filename); } return false; } /***************************************************************************************************************** * * Magic methods * /*****************************************************************************************************************/ /** * * * @param string $file * @param integer $length = 0 * @param string $delimiter = , * @param string $enclosure = " * @param string $escape = \ */ public function __construct ($file, $length=0, $delimiter=",", $enclosure='"', $escape='\\') { $this->filename = realpath((string)$file); $this->length = (int)$length; $this->delimiter = (string)$delimiter; $this->enclosure = (string)$enclosure; $this->escape = (string)$escape; $this->pointer = fopen((string)$file, "a+"); $this->setup(); } /** * Close the pointer * * */ public function __destruct () { fclose($this->pointer); } /** * Get a row * * @param integer $offset * * @return string */ public function __get ($offset) { return $this->offsetGet($offset); } /** * Set a row * * @param integer $offset * @param string $value */ public function __set ($offset, $value) { return $this->offsetSet($offset, $value); } /** * Check if a row is set * * @param integer $offset * @return boolean */ public function __isset ($offset) { return $this->offsetExists($offset); } /** * Remove a row * * @param integer $offset */ public function __unset ($offset) { return $this->offsetUnset($offset); } /** * Returns the entire file as a string * * @return string */ public function __toString () { $this->rewind(); $str = ""; foreach ($this as $row) { $str .= (string)$row; } return $str; } /** * * * @return array */ public function __sleep() { return array( "filename", "length", "delimiter", "enclosure", "escape", ); } /** * * * */ public function __wakeup () { $this->pointer = fopen($this->filename, "a+"); $this->setup(); } /***************************************************************************************************************** * * ArrayAccess * /*****************************************************************************************************************/ /** * Get a row * * @param integer|string $offset * @return string */ public function offsetGet ($offset) { if (strstr($offset, ",")) { $pos = explode(",", $offset, 2); return $this->slice((int)$pos[0], intval($pos[1] ? $pos[1] : $this->count())); } if (!is_numeric($offset)) { throw new InvalidArgumentException(); } $n = $this->count(); if ($offset > $n || $offset < 0) { throw new OutOfRangeException(); } $current = ftell($this->pointer); fseek($this->pointer, $this->lines[$offset], SEEK_SET); $arr = version_compare(PHP_VERSION, "5.3", ">=") ? fgetcsv($this->pointer, $this->length, $this->delimiter, $this->enclosure, $this->escape) : fgetcsv($this->pointer, $this->length, $this->delimiter, $this->enclosure);; fseek($this->pointer, $current, SEEK_SET); return new CSVIteratorRow($this, $offset, $arr); } /** * Set a row * * @param integer $offset * @param array|CSVIteratorRow $value */ public function offsetSet ($offset, $value) { if (!(is_array($value) || $value instanceof CSVIteratorRow)) { throw new InvalidArgumentException(); } if ($offset < 0) { throw new OutOfRangeException(); } $n = $this->count(); $fields = is_array($value) ? new CSVIteratorRow($this, is_numeric($offset) ? $offset : $n, $value) : $value; if (!is_numeric($offset) || $offset >= $n) { return $this->append($fields->toArray()); } $current = ftell($this->pointer); fseek($this->pointer, $this->lines[$offset], SEEK_SET); fremovecsv($this->pointer, $this->length, $this->delimiter, $this->enclosure); finsertcsv($this->pointer, $fields->toArray(), $this->delimiter, $this->enclosure, $this->escape); $this->setup(); fseek($this->pointer, $current, SEEK_SET); $this->next(); return; } /** * Check if a row exists * * @param integer $offset * @return boolean */ public function offsetExists ($offset) { return isset($this->lines[$offset]); } /** * * * */ public function offsetUnset ($offset) { if (!is_numeric($offset)) { throw new InvalidArgumentException(); } $n = $this->count(); if ($offset > $n || $offset < 0) { throw new OutOfRangeException(); } return $this->remove($offset); } /***************************************************************************************************************** * * Countable * /*****************************************************************************************************************/ /** * * * @return integer */ public function count () { return count($this->lines); } /***************************************************************************************************************** * * Iterator * /*****************************************************************************************************************/ /** * * * @return boolean */ public function valid () { return key($this->lines) !== null && is_array($this->currentRow); } /** * * * */ public function rewind () { reset($this->lines); rewind($this->pointer); $this->currentRow = version_compare(PHP_VERSION, "5.3", ">=") ? fgetcsv($this->pointer, $this->length, $this->delimiter, $this->enclosure, $this->escape) : fgetcsv($this->pointer, $this->length, $this->delimiter, $this->enclosure); return; } /** * * * @return integer */ public function key () { return key($this->lines); } /** * * * @return CSVIteratorRow */ public function current () { return new CSVIteratorRow($this, key($this->lines), $this->currentRow); } /** * * * */ public function next () { next($this->lines); $this->currentRow = version_compare(PHP_VERSION, "5.3", ">=") ? fgetcsv($this->pointer, $this->length, $this->delimiter, $this->enclosure, $this->escape) : fgetcsv($this->pointer, $this->length, $this->delimiter, $this->enclosure); return; } /***************************************************************************************************************** * * useful additions * /*****************************************************************************************************************/ /** * * * @return CSVIteratorRow */ public function prev () { fseek($this->pointer, prev($this->lines), SEEK_SET); $this->next(); return $this->current(); } /** * * * @return CSVIteratorRow */ public function end () { fseek($this->pointer, end($this->lines), SEEK_SET); $this->next(); return $this->current(); } /** * * * */ public function reset () { return $this->rewind(); } /** * * * @param integer $offset * * @return integer */ public function remove ($offset) { if (!isset($this->lines[$offset])) { throw new OutOfRangeException(); } $current = ftell($this->pointer); fseek($this->pointer, $this->lines[$offset], SEEK_SET); $ret = fremovecsv($this->pointer, $this->length, $this->delimiter, $this->enclosure, $this->escape); $this->setup(); fseek($this->pointer, $current, SEEK_SET); $this->next(); return $ret; } /** * * * @param array $fields * * @return integer */ public function append (array $fields) { $current = ftell($this->pointer); fseek($this->pointer, 0, SEEK_END); $count = count($fields); if ($count > $this->maxCount) { $this->maxCount = $count; } elseif ($count < $this->maxCount) { $fields = array_pad($fields, $this->maxCount, ""); $count = count($fields); } $ret = version_compare(PHP_VERSION, "5.3", "<") ? fputcsv($this->pointer, $fields, $this->delimiter, $this->enclosure) : fputcsv($this->pointer, $fields, $this->delimiter, $this->enclosure, $this->escape); $this->lines[] = $current; fseek($this->pointer, $current, SEEK_SET); return $ret; } /** * * * @param array $fields * * @return integer */ public function prepend (array $fields) { $current = ftell($this->pointer); fseek($this->pointer, 0, SEEK_SET); $ret = finsertcsv($this->pointer, $fields, $this->delimiter, $this->enclosure, $this->escape); $this->setup(); fseek($this->pointer, $current, SEEK_SET); $this->next(); return $ret; } /** * * * @param integer $offset * @param array $fields * * @return integer */ public function insert ($offset, array $fields) { if (!isset($this->lines[$offset])) { return $this->append($fields); } $current = ftell($this->pointer); fseek($this->pointer, $this->lines[$offset], SEEK_SET); $ret = finsertcsv($this->pointer, $fields, $this->delimiter, $this->enclosure, $this->escape); $this->setup(); fseek($this->pointer, $current, SEEK_SET); $this->next(); return $ret; } /** * * * @return string */ public function shift () { $x = $this[0]; $this->remove(0); return $x; } /** * * * @return string */ public function pop () { $l = $this->count()-1; $x = $this[$l]; $this->remove($l); return $x; } /** * * * */ public function push () { $args = func_get_args(); $i = 0; foreach($args as $arg) { $this->append($arg); $i += 1; } return $i; } /** * * * @param mixed ...args * * @return integer */ public function unshift () { $args = func_get_args(); $i = 0; foreach($args as $arg) { $this->prepend($arg); $i += 1; } return $i; } /** * * * @param integer $offset * @param integer $length * @return array */ public function slice ($offset, $length=null) { if (!isset($this->lines[$offset])) { throw new OutOfRangeException(); } $length = $length ? $length : $this->count(); $ret = array(); fseek($this->pointer, $this->lines[$offset]); while($length && !feof($this->pointer)) { $ret[] = fremovecsv($this->pointer, $this->length, $this->delimiter, $this->enclosure, $this->escape); $length -= 1; } $this->setup(); return $ret; } /** * * * @param integer $offset * @param integer $length * @param array $replacement - should always be an array of arrays * @return array */ public function splice ($offset, $length, array $replacement) { if (!isset($this->lines[$offset])) { throw new OutOfRangeException(); } reset($replacement); $ret = array(); fseek($this->pointer, $this->lines[$offset]); while($length && !feof($this->pointer)) { $ret[] = fremovecsv($this->pointer, $this->length, $this->delimiter, $this->enclosure, $this->escape); $length -= 1; } while ($replacement) { $insert = array_shift($replacement); if (!is_array($insert)) { throw new InvalidArgumentException(); } finsertcsv($this->pointer, $insert, $this->delimiter, $this->enclosure, $this->escape); } $this->setup(); return $ret; } /***************************************************************************************************************** * * Static methods * /*****************************************************************************************************************/ /** * * * @param array $arr * @param string $enclosure = " * @param string $escape = \ * * @return array */ public static function encapsulate (array $arr, $enclosure='"', $escape="\\") { $ret = array(); foreach($arr as $element) { $ret[] = $enclosure . str_replace($enclosure, $escape . $enclosure, $element) . $enclosure; } return $ret; } } /** * * * */ class CSVIteratorRow implements ArrayAccess, Iterator, Countable { /***************************************************************************************************************** * * properties * /*****************************************************************************************************************/ /** * * * @var CSVIterator */ protected $csv; /** * * * @var integer */ protected $row; /** * * * @var array */ protected $_array; /** * * * @var boolean */ protected $saveChanges = true; /** * * * */ public function normalize () { if (count($this->_array) < $this->csv->maxCount) { $this->_array = array_pad($this->_array, $this->csv->maxCount, ""); } return; } /***************************************************************************************************************** * * Magic methods * /*****************************************************************************************************************/ /** * * * @param CSVIterator $csv * @param integer $row * @param array $_array */ public function __construct (CSVIterator $csv, $row, array $_array) { $this->csv = $csv; $this->row = $row; $this->_array = $_array; return; } /** * * * @param integer $offset * * @return string */ public function __get ($offset) { return $this->_array[$offset]; } /** * * * @param integer $offset * @param string $value * * @return boolean */ public function __set ($offset, $value) { return $this->offsetSet($offset, $value); } /** * * * @return boolean */ public function __isset ($offset) { return $this->offsetExists($offset); } /** * * * */ public function __unset ($offset) { return $this->offsetUnset($offset); } /** * * * @return string */ public function __toString () { return implode($this->csv->delimiter, CSVIterator::encapsulate($this->_array, $this->csv->enclosure, $this->csv->escape)); } /***************************************************************************************************************** * * ArrayAccess * /*****************************************************************************************************************/ /** * * * @param integer $offset * * @return string */ public function offsetGet ($offset) { return $this->_array[$offset]; } /** * * * @param integer $offset * @param string $value */ public function offsetSet ($offset, $value) { if (!isset($this->_array[$offset]) || $this->_array[$offset] != $value) { $this->_array[$offset] = strval($value); $this->update(); } return; } /** * * * @param integer $offset * * @return boolean */ public function offsetExists ($offset) { return isset($this->_array[$offset]); } /** * * * @param integer $offset */ public function offsetUnset ($offset) { unset($this->_array[$offset]); $this->normalize(); $this->update(); return; } /***************************************************************************************************************** * * Countable * /*****************************************************************************************************************/ /** * * * @return integer */ public function count () { return count($this->_array); } /***************************************************************************************************************** * * Iterator * /*****************************************************************************************************************/ /** * * * @return boolean */ public function valid () { return key($this->_array) !== null; } /** * * * @return boolean */ public function rewind () { return reset($this->_array); } /** * * * @return integer */ public function key () { return key($this->_array); } /** * * * @return string */ public function current () { return current($this->_array); } /** * * * @return boolean */ public function next () { return next($this->_array); } /***************************************************************************************************************** * * useful additions * /*****************************************************************************************************************/ /** * * * @return string */ public function prev () { return prev($this->_array); } /** * * * @return string */ public function end () { return end($this->_array); } /** * * * */ public function reset () { return $this->rewind(); } /** * * * @param string $element * * @return boolean */ public function remove ($offset) { return $this->offsetUnset($offset) || true; } /** * * * @param string $element * * @return integer */ public function append ($element) { return $this->unshift($element); } /** * * * @param string $element * * @return integer */ public function prepend ($element) { return $this->push($element); } /** * * * @param integer $offset * @param string $element */ public function insert ($offset, $element) { return $this->offsetSet($offset, $element); } /** * * * @return string */ public function shift () { $x = array_shift($this->_array); $this->update(); return $x; } /** * * * @return string */ public function pop () { $x = array_pop($this->_array); $this->update(); return $x; } /** * * * @param mixed ...args * * @return integer */ public function push () { $args = func_get_args(); array_unshift($args, $this->_array); $x = call_user_func_array("array_push", $args); $this->update(); return $x; } /** * * * @param mixed ...args * * @return integer */ public function unshift () { $args = func_get_args(); array_unshift($args, $this->_array); $x = call_user_func_array("array_unshift", $args); $this->update(); return $x; } /** * * * @param mixed ...args * * @return mixed */ public function slice ($offset/*, $length=null*/) { $args = func_get_args(); array_unshift($args, $this->_array); $x = call_user_func_array("array_slice", $args); $this->update(); return $x; } /** * * * @param mixed ...args * * @return mixed */ public function splice ($offset, $length/*, $replacement=null*/) { $args = func_get_args(); array_unshift($args, $this->_array); $x = call_user_func_array("array_splice", $args); $this->update(); return $x; } /** * * * */ public function update () { if ($this->saveChanges) { $this->normalize(); return $this->csv->offsetSet($this->row, $this->_array); } } /** * * * */ public function toArray () { return $this->_array; } } ?>