shop.balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

spreadsheetreader.php (8488B)


      1 <?php
      2 /**
      3  * Main class for spreadsheet reading
      4  *
      5  * @version 0.5.10
      6  * @author Martins Pilsetnieks
      7  */
      8  
      9 namespace d_excel_reader_writer;
     10 
     11 use SpreadsheetReader_CSV;
     12 use SpreadsheetReader_ODS;
     13 use SpreadsheetReader_XLS;
     14 use SpreadsheetReader_XLSX;
     15 use SeekableIterator;
     16 use Countable;
     17 use OutOfBoundsException;
     18 use Exception;
     19  
     20 class SpreadsheetReader implements SeekableIterator, Countable {
     21 	const TYPE_XLSX = 'XLSX';
     22 	const TYPE_XLS = 'XLS';
     23 	const TYPE_CSV = 'CSV';
     24 	const TYPE_ODS = 'ODS';
     25 
     26 	private $Options = array(
     27 	'Delimiter' => '',
     28 	'Enclosure' => '"'
     29 	);
     30 
     31 	/**
     32 	 * @var int Current row in the file
     33 	 */
     34 	private $Index = 0;
     35 
     36 	/**
     37 	 * @var SpreadsheetReader_* Handle for the reader object
     38 	 */
     39 	private $Handle = array();
     40 
     41 	/**
     42 	 * @var TYPE_* Type of the contained spreadsheet
     43 	 */
     44 	private $Type = false;
     45 
     46 	/**
     47 	 * @param string Path to file
     48 	 * @param string Original filename (in case of an uploaded file), used to determine file type, optional
     49 	 * @param string MIME type from an upload, used to determine file type, optional
     50 	 */
     51 	public function __construct($Filepath, $OriginalFilename = false, $MimeType = false)
     52 	{
     53 	if (!is_readable($Filepath))
     54 	{
     55 	throw new Exception('SpreadsheetReader: File ('.$Filepath.') not readable');
     56 	}
     57 
     58 	// To avoid timezone warnings and exceptions for formatting dates retrieved from files
     59 	$DefaultTZ = @date_default_timezone_get();
     60 	if ($DefaultTZ)
     61 	{
     62 	date_default_timezone_set($DefaultTZ);
     63 	}
     64 
     65 	// Checking the other parameters for correctness
     66 
     67 	// This should be a check for string but we're lenient
     68 	if (!empty($OriginalFilename) && !is_scalar($OriginalFilename))
     69 	{
     70 	throw new Exception('SpreadsheetReader: Original file (2nd parameter) path is not a string or a scalar value.');
     71 	}
     72 	if (!empty($MimeType) && !is_scalar($MimeType))
     73 	{
     74 	throw new Exception('SpreadsheetReader: Mime type (3nd parameter) path is not a string or a scalar value.');
     75 	}
     76 
     77 	// 1. Determine type
     78 	if (!$OriginalFilename)
     79 	{
     80 	$OriginalFilename = $Filepath;
     81 	}
     82 
     83 	$Extension = strtolower(pathinfo($OriginalFilename, PATHINFO_EXTENSION));
     84 
     85 	switch ($MimeType)
     86 	{
     87 	case 'text/csv':
     88 	case 'text/comma-separated-values':
     89 	case 'text/plain':
     90 		$this -> Type = self::TYPE_CSV;
     91 		break;
     92 	case 'application/vnd.ms-excel':
     93 	case 'application/msexcel':
     94 	case 'application/x-msexcel':
     95 	case 'application/x-ms-excel':
     96 	case 'application/vnd.ms-excel':
     97 	case 'application/x-excel':
     98 	case 'application/x-dos_ms_excel':
     99 	case 'application/xls':
    100 	case 'application/xlt':
    101 	case 'application/x-xls':
    102 		// Excel does weird stuff
    103 		if (in_array($Extension, array('csv', 'tsv', 'txt')))
    104 		{
    105 		$this -> Type = self::TYPE_CSV;
    106 		}
    107 		else
    108 		{
    109 		$this -> Type = self::TYPE_XLS;
    110 		}
    111 		break;
    112 	case 'application/vnd.oasis.opendocument.spreadsheet':
    113 	case 'application/vnd.oasis.opendocument.spreadsheet-template':
    114 		$this -> Type = self::TYPE_ODS;
    115 		break;
    116 	case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
    117 	case 'application/vnd.openxmlformats-officedocument.spreadsheetml.template':
    118 	case 'application/xlsx':
    119 	case 'application/xltx':
    120 	$this -> Type = self::TYPE_XLSX;
    121 	break;
    122 	case 'application/xml':
    123 	// Excel 2004 xml format uses this
    124 		break;
    125 	}
    126 
    127 	if (!$this -> Type)
    128 	{
    129 	switch ($Extension)
    130 	{
    131 		case 'xlsx':
    132 		case 'xltx': // XLSX template
    133 		case 'xlsm': // Macro-enabled XLSX
    134 		case 'xltm': // Macro-enabled XLSX template
    135 		$this -> Type = self::TYPE_XLSX;
    136 		break;
    137 		case 'xls':
    138 		case 'xlt':
    139 		$this -> Type = self::TYPE_XLS;
    140 		break;
    141 		case 'ods':
    142 		case 'odt':
    143 		$this -> Type = self::TYPE_ODS;
    144 		break;
    145 		default:
    146 		$this -> Type = self::TYPE_CSV;
    147 			break;
    148 		}
    149 		}
    150 
    151 		// Pre-checking XLS files, in case they are renamed CSV or XLSX files
    152 		if ($this -> Type == self::TYPE_XLS)
    153 		{
    154 		self::Load(self::TYPE_XLS);
    155 		$this -> Handle = new SpreadsheetReader_XLS($Filepath);
    156 		if ($this -> Handle -> Error)
    157 		{
    158 			$this -> Handle -> __destruct();
    159 
    160 			if (is_resource($ZipHandle = zip_open($Filepath)))
    161 			{
    162 			$this -> Type = self::TYPE_XLSX;
    163 			zip_close($ZipHandle);
    164 			}
    165 			else
    166 			{
    167 			$this -> Type = self::TYPE_CSV;
    168 			}
    169 		}
    170 		}
    171 
    172 		// 2. Create handle
    173 		switch ($this -> Type)
    174 		{
    175 		case self::TYPE_XLSX:
    176 			self::Load(self::TYPE_XLSX);
    177 			$this -> Handle = new SpreadsheetReader_XLSX($Filepath);
    178 			break;
    179 		case self::TYPE_CSV:
    180 			self::Load(self::TYPE_CSV);
    181 			$this -> Handle = new SpreadsheetReader_CSV($Filepath, $this -> Options);
    182 			break;
    183 		case self::TYPE_XLS:
    184 			// Everything already happens above
    185 			break;
    186 		case self::TYPE_ODS:
    187 			self::Load(self::TYPE_ODS);
    188 			$this -> Handle = new SpreadsheetReader_ODS($Filepath, $this -> Options);
    189 			break;
    190 		}
    191 	}
    192 
    193 	/**
    194 	 * Gets information about separate sheets in the given file
    195 	 *
    196 	 * @return array Associative array where key is sheet index and value is sheet name
    197 	 */
    198 	public function Sheets()
    199 	{
    200 		return $this -> Handle -> Sheets();
    201 	}
    202 
    203 	/**
    204 	 * Changes the current sheet to another from the file.
    205 	 *	Note that changing the sheet will rewind the file to the beginning, even if
    206 	 *	the current sheet index is provided.
    207 	 *
    208 	 * @param int Sheet index
    209 	 *
    210 	 * @return bool True if sheet could be changed to the specified one,
    211 	 *	false if not (for example, if incorrect index was provided.
    212 	 */
    213 	public function ChangeSheet($Index)
    214 	{
    215 		return $this -> Handle -> ChangeSheet($Index);
    216 	}
    217 
    218 	/**
    219 	 * Autoloads the required class for the particular spreadsheet type
    220 	 *
    221 	 * @param TYPE_* Spreadsheet type, one of TYPE_* constants of this class
    222 	 */
    223 	private static function Load($Type)
    224 	{
    225 		if (!in_array($Type, array(self::TYPE_XLSX, self::TYPE_XLS, self::TYPE_CSV, self::TYPE_ODS)))
    226 		{
    227 		throw new Exception('SpreadsheetReader: Invalid type ('.$Type.')');
    228 		}
    229 
    230 		// 2nd parameter is to prevent autoloading for the class.
    231 		// If autoload works, the require line is unnecessary, if it doesn't, it ends badly.
    232 		if (!class_exists('SpreadsheetReader_'.$Type, false))
    233 		{
    234 		require(dirname(__FILE__).DIRECTORY_SEPARATOR.'spreadsheetreader/spreadsheetreader_'.strtolower($Type).'.php');
    235 		}
    236 	}
    237 
    238 	// !Iterator interface methods
    239 
    240 	/** 
    241 	 * Rewind the Iterator to the first element.
    242 	 * Similar to the reset() function for arrays in PHP
    243 	 */ 
    244 	public function rewind()
    245 	{
    246 		$this -> Index = 0;
    247 		if ($this -> Handle)
    248 		{
    249 		$this -> Handle -> rewind();
    250 		}
    251 	}
    252 
    253 	/** 
    254 	 * Return the current element.
    255 	 * Similar to the current() function for arrays in PHP
    256 	 *
    257 	 * @return mixed current element from the collection
    258 	 */
    259 	public function current()
    260 	{
    261 		if ($this -> Handle)
    262 		{
    263 		return $this -> Handle -> current();
    264 		}
    265 		return null;
    266 	}
    267 
    268 	/** 
    269 	 * Move forward to next element. 
    270 	 * Similar to the next() function for arrays in PHP 
    271 	 */ 
    272 	public function next()
    273 	{
    274 		if ($this -> Handle)
    275 		{
    276 		$this -> Index++;
    277 
    278 		return $this -> Handle -> next();
    279 		}
    280 		return null;
    281 	}
    282 
    283 	/** 
    284 	 * Return the identifying key of the current element.
    285 	 * Similar to the key() function for arrays in PHP
    286 	 *
    287 	 * @return mixed either an integer or a string
    288 	 */ 
    289 	public function key()
    290 	{
    291 		if ($this -> Handle)
    292 		{
    293 		return $this -> Handle -> key();
    294 		}
    295 		return null;
    296 	}
    297 
    298         public function setKey($index)
    299 	{
    300 		if ($this -> Handle)
    301 		{
    302 		return $this -> Handle -> setKey($index);
    303 		}
    304 		return null;
    305 	}
    306 
    307 	/** 
    308 	 * Check if there is a current element after calls to rewind() or next().
    309 	 * Used to check if we've iterated to the end of the collection
    310 	 *
    311 	 * @return boolean FALSE if there's nothing more to iterate over
    312 	 */ 
    313 	public function valid()
    314 	{
    315 		if ($this -> Handle)
    316 		{
    317 		return $this -> Handle -> valid();
    318 		}
    319 		return false;
    320 	}
    321 
    322 	// !Countable interface method
    323 	public function count()
    324 	{
    325 		if ($this -> Handle)
    326 		{
    327 		return $this -> Handle -> count();
    328 		}
    329 		return 0;
    330 	}
    331 
    332 	/**
    333 	 * Method for SeekableIterator interface. Takes a posiiton and traverses the file to that position
    334 	 * The value can be retrieved with a `current()` call afterwards.
    335 	 *
    336 	 * @param int Position in file
    337 	 */
    338 	public function seek($Position)
    339 	{
    340 		if (!$this -> Handle)
    341 		{
    342 		throw new OutOfBoundsException('SpreadsheetReader: No file opened');
    343 		}
    344 
    345 		$CurrentIndex = $this -> Handle -> key();
    346 
    347 		if ($CurrentIndex != $Position)
    348 		{
    349 		if ($Position < $CurrentIndex || is_null($CurrentIndex) || $Position == 0)
    350 		{
    351 			$this -> rewind();
    352 		}
    353 
    354 		while ($this -> Handle -> valid() && ($Position > $this -> Handle -> key()))
    355 		{
    356 			$this -> Handle -> next();
    357 		}
    358 
    359 		if (!$this -> Handle -> valid())
    360 		{
    361 			throw new OutOfBoundsException('SpreadsheetError: Position '.$Position.' not found');
    362 		}
    363 		}
    364 
    365 		return null;
    366 	}
    367 }
    368 
    369 ?>