<?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;
}
}
?>
|