Browse Source

seek溢出问题

liangjianshan 9 years ago
parent
commit
fdd830c145

+ 29 - 29
PHPExcelReader/PHPExcel/Autoloader.php

@@ -2,37 +2,37 @@
 /**
  * autoload library
  */
-
 PHPExcel_Autoloader::Register();
 
 class PHPExcel_Autoloader {
-	/**
-	 * Register the Autoloader with SPL
-	 */
-	public static function Register() {
-		if (function_exists('__autoload')) {
-			spl_autoload_register('__autoload');
-		}
+    /**
+     * Register the Autoloader with SPL
+     */
+    public static function Register() {
+        if (function_exists('__autoload')) {
+            spl_autoload_register('__autoload');
+        }
+
+        return spl_autoload_register(array('PHPExcel_Autoloader', 'Load'));
+    }
+
+    /**
+     * Autoload a class identified by name
+     *
+     * @param string $pClassName Name of the object to load
+     * @return string
+     */
+    public static function Load($pClassName) {
+        if ((class_exists($pClassName, false)) || (strpos($pClassName, 'PHPExcel') !== 0)) {
+            return false;
+        }
+
+        $pClassFilePath = PHPEXCEL_ROOT . str_replace('_', DIRECTORY_SEPARATOR, $pClassName) . '.php';
 
-		return spl_autoload_register(array('PHPExcel_Autoloader', 'Load'));
-	}
-	
-	
-	/**
-	 * Autoload a class identified by name
-	 * @param	string	$pClassName	Name of the object to load
-	 */
-	public static function Load($pClassName){
-		if ((class_exists($pClassName, FALSE)) || (strpos($pClassName, 'PHPExcel') !== 0)) {
-			return FALSE;
-		}
-	
-		$pClassFilePath = PHPEXCEL_ROOT . str_replace('_', DIRECTORY_SEPARATOR, $pClassName) . '.php';
+        if ((file_exists($pClassFilePath) === false) || (is_readable($pClassFilePath) === false)) {
+            return false;
+        }
 
-		if ((file_exists($pClassFilePath) === FALSE) || (is_readable($pClassFilePath) === FALSE)) {
-			return FALSE;
-		}
-	
-		require $pClassFilePath;
-	}
-}
+        require $pClassFilePath;
+    }
+}

+ 222 - 207
PHPExcelReader/PHPExcel/Reader/CSV.php

@@ -1,213 +1,228 @@
 <?php
 
 class PHPExcel_Reader_CSV implements Iterator, Countable {
-	private $_inputEncoding	= 'UTF-8';
-	private $_delimiter	= ',';
-	private $_enclosure	= '"';
-	private $_filter = 0;
-
-	/**
-	 * @param string $filePath
-	 * @param int $filter  filter empty row
-	 * @throws Exception
-	 */
-	public function __construct($filePath, $filter = 0) {
-		if ( ! file_exists($filePath)) {
-			throw new Exception("Could not open " . $filePath . " for reading! File does not exist.");
-		}
-
-		$this->filePath = $filePath;
-		$this->_filter = $filter;
-		ini_set('auto_detect_line_endings', true);
-		
-		$this->_fileHandle = fopen($filePath,  'r');
-		$this->_detectEncoding();
-	}
-
-	/**
-	 * Move filepointer past any BOM marker
-	 *
-	 */
-	private function _detectEncoding()
-	{
-		$step = 0;
-		while ($step < 3) {
-			$BOM = bin2hex(fread($this->_fileHandle, 2 + $step++));
-			rewind($this->_fileHandle);
-			if ($BOM == 'fffe' || $BOM == 'feff') {
-				$BOMLength = 2;
-				$this->_delimiter = "\t";
-				$this->_inputEncoding = 'UTF-16';
-				break;
-			}
-			else if ($BOM == 'efbbbf') {
-				$BOMLength = 3;
-				break;
-			}
-			else if ($BOM == '0000feff' || $BOM == 'fffe0000') {
-				$BOMLength = 4;
-				$this->_delimiter = "\t";
-				$this->_inputEncoding = 'UTF-32';
-				break;
-			}
-		}
-
-		if ( ! $BOMLength) {
-			$encoding = mb_detect_encoding(fgets($this->_fileHandle, 1024), 'ASCII, UTF-8, GB2312, GBK');
-			rewind($this->_fileHandle);
-			if ($encoding) {
-				if ($encoding == 'EUC-CN') {
-					$this->_inputEncoding = 'GB2312';
-				}
-				else if ($encoding == 'CP936') {
-					$this->_inputEncoding = 'GBK';
-				}
-				else {
-					$this->_inputEncoding = $encoding;
-				}
-			}
-		}
-
-		if ($this->_inputEncoding != 'UTF-8') {
-			stream_filter_register("convert_iconv.*", "convert_iconv_filter");
-			stream_filter_append($this->_fileHandle, 'convert_iconv.' . $this->_inputEncoding . '/UTF-8');
-		}
-	}
-
-	/**
-	 * Returns information about sheets in the file.
-	 * @return array
-	 */
-	public function Sheets() {
-		return array(0 => basename($this->filePath));
-	}
-
-	/**
-	 * Changes sheet to another.
-	 * @param bool
-	 */
-	public function ChangeSheet($index) {
-		if ($index == 0) {
-			$this->rewind();
-			return true;
-		}
-
-		return false;
-	}
-
-	/**
-	 * Rewind the Iterator to the first element.
-	 */
-	public function rewind() {
-		rewind($this->_fileHandle);
-		$this->currentRow = null;
-		$this->index = 0;
-	}
-
-	/**
-	 * Return the current element.
-	 * @return mixed
-	 */
-	public function current() {
-		if ($this->index == 0 && ! isset($this->currentRow)) {
-			$this->rewind();
-			$this->next();
-			$this->index = 0;
-		}
-
-		return $this->currentRow;
-	}
-
-	/**
-	 * Move forward to next element.
-	 */
-	public function next() {
-		$this->currentRow = array();
-
-		$this->index++;
-		while (($row = fgetcsv($this->_fileHandle, 0, $this->_delimiter, $this->_enclosure)) !== false) {
-			if ( ! $this->_filter || array_filter($row, array($this, 'filter'))) {
-				$this->currentRow = $row;
-				break;
-			}
-		}
-
-		return $this->currentRow;
-	}
-
-	/**
-	 * Return the identifying key of the current element.
-	 * @return mixed
-	 */
-	public function key() {
-		return $this->index;
-	}
-
-	/**
-	 * Check if there is a current element after calls to rewind() or next().
-	 * @return bool
-	 */
-	public function valid() {
-		if ($this->currentRow || ! feof($this->_fileHandle)) {
-			return true;
-		}
-		else {
-			fclose($this->_fileHandle);
-			return false;
-		}
-	}
-
-	/**
-	 * return the count of the contained items
-	 * @return int
-	 */
-	public function count() {
-		if ( ! isset($this->rowCount)) {
-			$total = 0;
-
-			rewind($this->_fileHandle);
-			while (($row = fgetcsv($this->_fileHandle, 0, $this->_delimiter, $this->_enclosure)) !== false) {				
-				if ( ! $this->_filter || array_filter($row, array($this, 'filter'))) {
-					$total++;
-				}
-			}
-
-			$this->rowCount = $total;
-		}
-
-		return $this->rowCount;
-	}
-
-	/**
-	 * filter empty string
-	 * @param mixed $value
-	 * @return boolean
-	 */
-	private function filter($value) {
-		return trim($value) !== '';
-	}
+    private $_fileHandle = null;
+
+    private $filePath = '';
+
+    private $_inputEncoding = 'UTF-8';
+
+    private $_delimiter = ',';
+
+    private $_enclosure = '"';
+
+    private $_filter = 0;
+
+    /**
+     * @param string $filePath
+     * @param int $filter filter empty row
+     *
+     * @throws Exception
+     */
+    public function __construct($filePath, $filter = 0) {
+        if (! file_exists($filePath)) {
+            throw new Exception("Could not open " . $filePath . " for reading! File does not exist.");
+        }
+
+        $this->filePath = $filePath;
+        $this->_filter = $filter;
+        ini_set('auto_detect_line_endings', true);
+
+        $this->_fileHandle = fopen($filePath, 'r');
+        $this->_detectEncoding();
+    }
+
+    /**
+     * Move filepointer past any BOM marker
+     */
+    private function _detectEncoding() {
+        $step = $BOMLength = 0;
+        while ($step < 3) {
+            $BOM = bin2hex(fread($this->_fileHandle, 2 + $step++));
+
+            rewind($this->_fileHandle);
+
+            if ($BOM == 'fffe' || $BOM == 'feff') {
+                $BOMLength = 2;
+                $this->_delimiter = "\t";
+                $this->_inputEncoding = 'UTF-16';
+                break;
+            } else {
+                if ($BOM == 'efbbbf') {
+                    $BOMLength = 3;
+                    break;
+                } else {
+                    if ($BOM == '0000feff' || $BOM == 'fffe0000') {
+                        $BOMLength = 4;
+                        $this->_delimiter = "\t";
+                        $this->_inputEncoding = 'UTF-32';
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (! $BOMLength) {
+            $encoding = mb_detect_encoding(fgets($this->_fileHandle, 1024), 'ASCII, UTF-8, GB2312, GBK');
+            rewind($this->_fileHandle);
+            if ($encoding) {
+                if ($encoding == 'EUC-CN') {
+                    $this->_inputEncoding = 'GB2312';
+                } else {
+                    if ($encoding == 'CP936') {
+                        $this->_inputEncoding = 'GBK';
+                    } else {
+                        $this->_inputEncoding = $encoding;
+                    }
+                }
+            }
+        }
+
+        if ($this->_inputEncoding != 'UTF-8') {
+            stream_filter_register("convert_iconv.*", "convert_iconv_filter");
+            stream_filter_append($this->_fileHandle, 'convert_iconv.' . $this->_inputEncoding . '/UTF-8');
+        }
+    }
+
+    /**
+     * Returns information about sheets in the file.
+     * @return array
+     */
+    public function Sheets() {
+        return array(0 => basename($this->filePath));
+    }
+
+    /**
+     * Changes sheet to another.
+     *
+     * @param int $index
+     * @return bool
+     */
+    public function ChangeSheet($index) {
+        if ($index == 0) {
+            $this->rewind();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Rewind the Iterator to the first element.
+     */
+    public function rewind() {
+        rewind($this->_fileHandle);
+        $this->currentRow = null;
+        $this->index = 0;
+    }
+
+    /**
+     * Return the current element.
+     * @return mixed
+     */
+    public function current() {
+        if ($this->index == 0 && ! isset($this->currentRow)) {
+            $this->rewind();
+            $this->next();
+            $this->index = 0;
+        }
+
+        return $this->currentRow;
+    }
+
+    /**
+     * Move forward to next element.
+     */
+    public function next() {
+        $this->currentRow = array();
+
+        $this->index++;
+        while (($row = fgetcsv($this->_fileHandle, 0, $this->_delimiter, $this->_enclosure)) !== false) {
+            if (! $this->_filter || array_filter($row, array($this, 'filter'))) {
+                $this->currentRow = $row;
+                break;
+            }
+        }
+
+        return $this->currentRow;
+    }
+
+    /**
+     * Return the identifying key of the current element.
+     * @return mixed
+     */
+    public function key() {
+        return $this->index;
+    }
+
+    /**
+     * Check if there is a current element after calls to rewind() or next().
+     * @return bool
+     */
+    public function valid() {
+        if ($this->currentRow || ! feof($this->_fileHandle)) {
+            return true;
+        } else {
+            fclose($this->_fileHandle);
+
+            return false;
+        }
+    }
+
+    /**
+     * return the count of the contained items
+     * @return int
+     */
+    public function count() {
+        if (! isset($this->rowCount)) {
+            $total = 0;
+            rewind($this->_fileHandle);
+            while (($row = fgetcsv($this->_fileHandle, 0, $this->_delimiter, $this->_enclosure)) !== false) {
+                if (! $this->_filter || array_filter($row, array($this, 'filter'))) {
+                    $total++;
+                }
+            }
+
+            $this->rowCount = $total;
+        }
+
+        return $this->rowCount;
+    }
+
+    /**
+     * filter empty string
+     *
+     * @param mixed $value
+     *
+     * @return boolean
+     */
+    private function filter($value) {
+        return trim($value) !== '';
+    }
 }
 
 class convert_iconv_filter extends php_user_filter {
-	private $modes;
-
-	function filter($in, $out, &$consumed, $closing) {
-		while ($bucket = stream_bucket_make_writeable($in)) {
-			$bucket->data = mb_convert_encoding($bucket->data, $this->modes[1], $this->modes[0]);
-			$consumed += $bucket->datalen;
-			stream_bucket_append($out, $bucket);
-		}
-		return PSFS_PASS_ON;
-	}
-
-	function onCreate() {
-		$format = explode('/', substr($this->filtername, 14));
-		if (count($format) == 2) {
-			$this->modes = $format;
-			return true;
-		}
-		else {
-			return false;
-		}
-	}
-}
+    private $modes;
+
+    function filter($in, $out, &$consumed, $closing) {
+        while ($bucket = stream_bucket_make_writeable($in)) {
+            $bucket->data = mb_convert_encoding($bucket->data, $this->modes[1], $this->modes[0]);
+            $consumed += $bucket->datalen;
+            stream_bucket_append($out, $bucket);
+        }
+
+        return PSFS_PASS_ON;
+    }
+
+    function onCreate() {
+        $format = explode('/', substr($this->filtername, 14));
+        if (count($format) == 2) {
+            $this->modes = $format;
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

+ 1 - 1
PHPExcelReader/PHPExcel/Reader/Excel5.php

@@ -589,7 +589,7 @@ class PHPExcel_Reader_Excel5 {
 			return pow($key,$value);
 		}
 	}
-	
+
 	//
 	//	Private method to return an array of the factors of the input value
 	//

+ 4 - 3
PHPExcelReader/PHPExcel/Reader/XLS.php

@@ -62,8 +62,9 @@ class PHPExcel_Reader_XLS implements Iterator, Countable {
 	 */
 	public function current() {
 		if ($this->index == 0 && ! isset($this->currentRow)) {
+			$this->rewind();
 			$this->next();
-			$this->index = 0;
+			$this->Index--;
 		}
 
 		return $this->currentRow;
@@ -99,12 +100,12 @@ class PHPExcel_Reader_XLS implements Iterator, Countable {
 	 * Check if there is a current element after calls to rewind() or next().
 	 * @return boolean
 	 */
-	public function valid()	{
+	public function valid() {
 		if ($this->error) {
 			return false;
 		}
 
-		return ($this->index < $this->count());
+		return ($this->index <= $this->count());
 	}
 
 	/**

+ 992 - 1233
PHPExcelReader/PHPExcel/Reader/XLSX.php

@@ -1,1237 +1,996 @@
 <?php
 
 class PHPExcel_Reader_XLSX implements Iterator, Countable {
-	const CELL_TYPE_BOOL = 'b';
-	const CELL_TYPE_NUMBER = 'n';
-	const CELL_TYPE_ERROR = 'e';
-	const CELL_TYPE_SHARED_STR = 's';
-	const CELL_TYPE_STR = 'str';
-	const CELL_TYPE_INLINE_STR = 'inlineStr';
-
-	/**
-	 * Number of shared strings that can be reasonably cached, i.e., that aren't read from file but stored in memory.
-	 *	If the total number of shared strings is higher than this, caching is not used.
-	 *	If this value is null, shared strings are cached regardless of amount.
-	 *	With large shared string caches there are huge performance gains, however a lot of memory could be used which
-	 *	can be a problem, especially on shared hosting.
-	 */
-	const SHARED_STRING_CACHE_LIMIT = 50000;
-
-	private $Options = array(
-		'TempDir' => '',
-		'ReturnDateTimeObjects' => false
-	);
-
-	private static $RuntimeInfo = array(
-		'GMPSupported' => false
-	);
-
-	private $Valid = false;
-
-	/**
-	 * @var SpreadsheetReader_* Handle for the reader object
-	 */
-	private $Handle = false;
-
-	// Worksheet file
-	/**
-	 * @var string Path to the worksheet XML file
-	 */
-	private $WorksheetPath = false;
-	/**
-	 * @var XMLReader XML reader object for the worksheet XML file
-	 */
-	private $Worksheet = false;
-
-	// Shared strings file
-	/**
-	 * @var string Path to shared strings XML file
-	 */
-	private $SharedStringsPath = false;
-	/**
-	 * @var XMLReader XML reader object for the shared strings XML file
-	 */
-	private $SharedStrings = false;
-	/**
-	 * @var array Shared strings cache, if the number of shared strings is low enough
-	 */
-	private $SharedStringCache = array();
-
-	// Workbook data
-	/**
-	 * @var SimpleXMLElement XML object for the workbook XML file
-	 */
-	private $WorkbookXML = false;
-
-	// Style data
-	/**
-	 * @var SimpleXMLElement XML object for the styles XML file
-	 */
-	private $StylesXML = false;
-	/**
-	 * @var array Container for cell value style data
-	 */
-	private $Styles = array();
-
-	private $TempDir = '';
-	private $TempFiles = array();
-
-	private $CurrentRow = false;
-	private $rowCount = null;
-	
-	// Runtime parsing data
-	/**
-	 * @var int Current row in the file
-	 */
-	private $Index = 0;
-
-	/**
-	 * @var array Data about separate sheets in the file
-	 */
-	private $Sheets = false;
-
-	private $SharedStringCount = 0;
-	private $SharedStringIndex = 0;
-	private $LastSharedStringValue = null;
-
-	private $RowOpen = false;
-
-	private $SSOpen = false;
-	private $SSForwarded = false;
-
-	private static $BuiltinFormats = array(
-		0 => '',
-		1 => '0',
-		2 => '0.00',
-		3 => '#,##0',
-		4 => '#,##0.00',
-
-		9 => '0%',
-		10 => '0.00%',
-		11 => '0.00E+00',
-		12 => '# ?/?',
-		13 => '# ??/??',
-		14 => 'yyyy/m/d',
-		15 => 'd-mmm-yy',
-		16 => 'd-mmm',
-		17 => 'mmm-yy',
-		18 => 'h:mm AM/PM',
-		19 => 'h:mm:ss AM/PM',
-		20 => 'h:mm',
-		21 => 'h:mm:ss',
-		22 => 'yyyy/m/d h:mm',
-		
-		31 => 'yyyy年m月d日',
-		32 => 'h时mmi分',
-		33 => 'h时mmi分ss秒',
-
-		37 => '#,##0 ;(#,##0)',
-		38 => '#,##0 ;[Red](#,##0)',
-		39 => '#,##0.00;(#,##0.00)',
-		40 => '#,##0.00;[Red](#,##0.00)',
-
-		44 => '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)',
-		45 => 'mm:ss',
-		46 => '[h]:mm:ss',
-		47 => 'mm:ss.0',
-		48 => '##0.0E+0',
-		49 => '@',
-
-		55 => 'AM/PM h时mmi分',
-		56 => 'AM/PM h时mmi分ss秒',
-		58 => 'm月d日',
-
-		// CHT & CHS
-		27 => 'yyyy年m月',
-		30 => 'm/d/yy',
-		36 => '[$-404]e/m/d',
-		50 => '[$-404]e/m/d',
-		57 => '[$-404]e/m/d',
-
-		// THA
-		59 => 't0',
-		60 => 't0.00',
-		61 => 't#,##0',
-		62 => 't#,##0.00',
-		67 => 't0%',
-		68 => 't0.00%',
-		69 => 't# ?/?',
-		70 => 't# ??/??'
-	);
-	private $Formats = array();
-
-	private static $DateReplacements = array(
-		'All' => array(
-			'\\' => '',
-			'am/pm' => 'A',
-			'e' => 'Y',
-			'yyyy' => 'Y',
-			'yy' => 'y',
-			'mmmmm' => 'M',
-			'mmmm' => 'F',
-			'mmm' => 'M',
-			':mm' => ':i',
-			'mmi'	=> 'i',
-			'mm' => 'm',
-			'm' => 'n',
-			'dddd' => 'l',
-			'ddd' => 'D',
-			'dd' => 'd',
-			'd' => 'j',
-			'ss' => 's',
-			'.s' => ''
-		),
-		'24H' => array(
-			'hh' => 'H',
-			'h' => 'G'
-		),
-		'12H' => array(
-			'hh' => 'h',
-			'h' => 'g'
-		)
-	);
-
-	private static $BaseDate = false;
-	private static $DecimalSeparator = '.';
-	private static $ThousandSeparator = ',';
-	private static $CurrencyCode = '';
-
-	/**
-	 * @var array Cache for already processed format strings
-	 */
-	private $ParsedFormatCache = array();
-
-	/**
-	 * @param string Path to file
-	 * @param array Options:
-	 *	TempDir => string Temporary directory path
-	 *	ReturnDateTimeObjects => bool True => dates and times will be returned as PHP DateTime objects, false => as strings
-	 */
-	public function __construct($Filepath, array $Options = null)
-	{
-		if (!is_readable($Filepath))
-		{
-			throw new Exception('SpreadsheetReader_XLSX: File not readable ('.$Filepath.')');
-		}
-
-		$this -> TempDir = isset($Options['TempDir']) && is_writable($Options['TempDir']) ?
-			$Options['TempDir'] :
-			sys_get_temp_dir();
-
-		$this -> TempDir = rtrim($this -> TempDir, DIRECTORY_SEPARATOR);
-		$this -> TempDir = $this -> TempDir.DIRECTORY_SEPARATOR.uniqid().DIRECTORY_SEPARATOR;
-
-		$Zip = new ZipArchive;
-		$Status = $Zip -> open($Filepath);
-
-		if ($Status !== true)
-		{
-			throw new Exception('SpreadsheetReader_XLSX: File not readable ('.$Filepath.') (Error '.$Status.')');
-		}
-
-		// Getting the general workbook information
-		if ($Zip -> locateName('xl/workbook.xml') !== false)
-		{
-			$this -> WorkbookXML = new SimpleXMLElement($Zip -> getFromName('xl/workbook.xml'));
-		}
-
-		// Extracting the XMLs from the XLSX zip file
-		if ($Zip -> locateName('xl/sharedStrings.xml') !== false)
-		{
-			$this -> SharedStringsPath = $this -> TempDir.'xl'.DIRECTORY_SEPARATOR.'sharedStrings.xml';
-			$Zip -> extractTo($this -> TempDir, 'xl/sharedStrings.xml');
-			$this -> TempFiles[] = $this -> TempDir.'xl'.DIRECTORY_SEPARATOR.'sharedStrings.xml';
-
-			if (is_readable($this -> SharedStringsPath))
-			{
-				$this -> SharedStrings = new XMLReader;
-				$this -> SharedStrings -> open($this -> SharedStringsPath);
-				$this -> PrepareSharedStringCache();
-			}
-		}
-
-		$Sheets = $this -> Sheets();
-
-		foreach ($this -> Sheets as $Index => $Name)
-		{
-			if ($Zip -> locateName('xl/worksheets/sheet'.$Index.'.xml') !== false)
-			{
-				$Zip -> extractTo($this -> TempDir, 'xl/worksheets/sheet'.$Index.'.xml');
-				$this -> TempFiles[] = $this -> TempDir.'xl'.DIRECTORY_SEPARATOR.'worksheets'.DIRECTORY_SEPARATOR.'sheet'.$Index.'.xml';
-			}
-		}
-
-		$this -> ChangeSheet(0);
-
-		// If worksheet is present and is OK, parse the styles already
-		if ($Zip -> locateName('xl/styles.xml') !== false)
-		{
-			$this -> StylesXML = new SimpleXMLElement($Zip -> getFromName('xl/styles.xml'));
-			if ($this -> StylesXML && $this -> StylesXML -> cellXfs && $this -> StylesXML -> cellXfs -> xf)
-			{
-				foreach ($this -> StylesXML -> cellXfs -> xf as $Index => $XF)
-				{
-					// Format #0 is a special case - it is the "General" format that is applied regardless of applyNumberFormat
-					if ($XF -> attributes() -> applyNumberFormat || (0 == (int)$XF -> attributes() -> numFmtId))
-					{
-						$FormatId = (int)$XF -> attributes() -> numFmtId;
-						// If format ID >= 164, it is a custom format and should be read from styleSheet\numFmts
-						$this -> Styles[] = $FormatId;
-					}
-					else
-					{
-						// 0 for "General" format
-						$this -> Styles[] = 0;
-					}
-				}
-			}
-
-			if ($this -> StylesXML -> numFmts && $this -> StylesXML -> numFmts -> numFmt)
-			{
-				foreach ($this -> StylesXML -> numFmts -> numFmt as $Index => $NumFmt)
-				{
-					$this -> Formats[(int)$NumFmt -> attributes() -> numFmtId] = (string)$NumFmt -> attributes() -> formatCode;
-				}
-			}
-
-			unset($this -> StylesXML);
-		}
-
-		$Zip -> close();
-
-		// Setting base date
-		if (!self::$BaseDate)
-		{
-			self::$BaseDate = new DateTime;
-			self::$BaseDate -> setTimezone(new DateTimeZone('UTC'));
-			self::$BaseDate -> setDate(1900, 1, 0);
-			self::$BaseDate -> setTime(0, 0, 0);
-		}
-
-		// Decimal and thousand separators
-		if (!self::$DecimalSeparator && !self::$ThousandSeparator && !self::$CurrencyCode)
-		{
-			$Locale = localeconv();
-			self::$DecimalSeparator = $Locale['decimal_point'];
-			self::$ThousandSeparator = $Locale['thousands_sep'];
-			self::$CurrencyCode = $Locale['int_curr_symbol'];
-		}
-
-		if (function_exists('gmp_gcd'))
-		{
-			self::$RuntimeInfo['GMPSupported'] = true;
-		}
-	}
-
-	/**
-	 * Destructor, destroys all that remains (closes and deletes temp files)
-	 */
-	public function __destruct()
-	{
-		foreach ($this -> TempFiles as $TempFile)
-		{
-			@unlink($TempFile);
-		}
-
-		// Better safe than sorry - shouldn't try deleting '.' or '/', or '..'.
-		if (strlen($this -> TempDir) > 2)
-		{
-			@rmdir($this -> TempDir.'xl'.DIRECTORY_SEPARATOR.'worksheets');
-			@rmdir($this -> TempDir.'xl');
-			@rmdir($this -> TempDir);
-		}
-
-		if ($this -> Worksheet && $this -> Worksheet instanceof XMLReader)
-		{
-			$this -> Worksheet -> close();
-			unset($this -> Worksheet);
-		}
-		unset($this -> WorksheetPath);
-
-		if ($this -> SharedStrings && $this -> SharedStrings instanceof XMLReader)
-		{
-			$this -> SharedStrings -> close();
-			unset($this -> SharedStrings);
-		}
-		unset($this -> SharedStringsPath);
-
-		if (isset($this -> StylesXML))
-		{
-			unset($this -> StylesXML);
-		}
-		if ($this -> WorkbookXML)
-		{
-			unset($this -> WorkbookXML);
-		}
-	}
-
-	/**
-	 * Retrieves an array with information about sheets in the current file
-	 *
-	 * @return array List of sheets (key is sheet index, value is name)
-	 */
-	public function Sheets()
-	{
-		if ($this -> Sheets === false)
-		{
-			$this -> Sheets = array();
-			foreach ($this -> WorkbookXML -> sheets -> sheet as $Index => $Sheet)
-			{
-				$Attributes = $Sheet -> attributes('r', true);
-				foreach ($Attributes as $Name => $Value)
-				{
-					if ($Name == 'id')
-					{
-						$SheetID = (int)str_replace('rId', '', (string)$Value);
-						break;
-					}
-				}
-
-				$this -> Sheets[$SheetID] = (string)$Sheet['name'];
-			}
-			ksort($this -> Sheets);
-		}
-		return array_values($this -> Sheets);
-	}
-
-	/**
-	 * Changes the current sheet in the file to another
-	 *
-	 * @param int Sheet index
-	 *
-	 * @return bool True if sheet was successfully changed, false otherwise.
-	 */
-	public function ChangeSheet($Index)
-	{
-		$RealSheetIndex = false;
-		$Sheets = $this -> Sheets();
-		if (isset($Sheets[$Index]))
-		{
-			$SheetIndexes = array_keys($this -> Sheets);
-			$RealSheetIndex = $SheetIndexes[$Index];
-		}
-
-		$TempWorksheetPath = $this -> TempDir.'xl/worksheets/sheet'.$RealSheetIndex.'.xml';
-
-		if ($RealSheetIndex !== false && is_readable($TempWorksheetPath))
-		{
-			$this -> WorksheetPath = $TempWorksheetPath;
-
-			$this -> rewind();
-			return true;
-		}
-
-		return false;
-	}
-
-	/**
-	 * Creating shared string cache if the number of shared strings is acceptably low (or there is no limit on the amount
-	 */
-	private function PrepareSharedStringCache()
-	{
-		while ($this -> SharedStrings -> read())
-		{
-			if ($this -> SharedStrings -> name == 'sst')
-			{
-				$this -> SharedStringCount = $this -> SharedStrings -> getAttribute('count');
-				break;
-			}
-		}
-
-		if (!$this -> SharedStringCount || (self::SHARED_STRING_CACHE_LIMIT < $this -> SharedStringCount && self::SHARED_STRING_CACHE_LIMIT !== null))
-		{
-			return false;
-		}
-
-		$CacheIndex = 0;
-		$CacheValue = '';
-		while ($this -> SharedStrings -> read())
-		{
-			switch ($this -> SharedStrings -> name)
-			{
-				case 'si':
-					if ($this -> SharedStrings -> nodeType == XMLReader::END_ELEMENT)
-					{
-						$this -> SharedStringCache[$CacheIndex] = $CacheValue;
-						$CacheIndex++;
-						$CacheValue = '';
-					}
-					break;
-				case 't':
-					if ($this -> SharedStrings -> nodeType == XMLReader::END_ELEMENT)
-					{
-						continue;
-					}
-					$CacheValue .= $this -> SharedStrings -> readString();
-					break;
-			}
-		}
-
-		$this -> SharedStrings -> close();
-		return true;
-	}
-
-	/**
-	 * Retrieves a shared string value by its index
-	 *
-	 * @param int Shared string index
-	 *
-	 * @return string Value
-	 */
-	private function GetSharedString($Index)
-	{
-		if ((self::SHARED_STRING_CACHE_LIMIT === null || self::SHARED_STRING_CACHE_LIMIT > 0) && !empty($this -> SharedStringCache))
-		{
-			if (isset($this -> SharedStringCache[$Index]))
-			{
-				return $this -> SharedStringCache[$Index];
-			}
-			else
-			{
-				return '';
-			}
-		}
-
-		// If the desired index is before the current, rewind the XML
-		if ($this -> SharedStringIndex > $Index)
-		{
-			$this -> SSOpen = false;
-			$this -> SharedStrings -> close();
-			$this -> SharedStrings -> open($this -> SharedStringsPath);
-			$this -> SharedStringIndex = 0;
-			$this -> LastSharedStringValue = null;
-			$this -> SSForwarded = false;
-		}
-
-		// Finding the unique string count (if not already read)
-		if ($this -> SharedStringIndex == 0 && !$this -> SharedStringCount)
-		{
-			while ($this -> SharedStrings -> read())
-			{
-				if ($this -> SharedStrings -> name == 'sst')
-				{
-					$this -> SharedStringCount = $this -> SharedStrings -> getAttribute('uniqueCount');
-					break;
-				}
-			}
-		}
-
-		// If index of the desired string is larger than possible, don't even bother.
-		if ($this -> SharedStringCount && ($Index >= $this -> SharedStringCount))
-		{
-			return '';
-		}
-
-		// If an index with the same value as the last already fetched is requested
-		// (any further traversing the tree would get us further away from the node)
-		if (($Index == $this -> SharedStringIndex) && ($this -> LastSharedStringValue !== null))
-		{
-			return $this -> LastSharedStringValue;
-		}
-
-		// Find the correct <si> node with the desired index
-		while ($this -> SharedStringIndex <= $Index)
-		{
-			// SSForwarded is set further to avoid double reading in case nodes are skipped.
-			if ($this -> SSForwarded)
-			{
-				$this -> SSForwarded = false;
-			}
-			else
-			{
-				$ReadStatus = $this -> SharedStrings -> read();
-				if (!$ReadStatus)
-				{
-					break;
-				}
-			}
-
-			if ($this -> SharedStrings -> name == 'si')
-			{
-				if ($this -> SharedStrings -> nodeType == XMLReader::END_ELEMENT)
-				{
-					$this -> SSOpen = false;
-					$this -> SharedStringIndex++;
-				}
-				else
-				{
-					$this -> SSOpen = true;
-
-					if ($this -> SharedStringIndex < $Index)
-					{
-						$this -> SSOpen = false;
-						$this -> SharedStrings -> next('si');
-						$this -> SSForwarded = true;
-						$this -> SharedStringIndex++;
-						continue;
-					}
-					else
-					{
-						break;
-					}
-				}
-			}
-		}
-
-		$Value = '';
-
-		// Extract the value from the shared string
-		if ($this -> SSOpen && ($this -> SharedStringIndex == $Index))
-		{
-			while ($this -> SharedStrings -> read())
-			{
-				switch ($this -> SharedStrings -> name)
-				{
-					case 't':
-						if ($this -> SharedStrings -> nodeType == XMLReader::END_ELEMENT)
-						{
-							continue;
-						}
-						$Value .= $this -> SharedStrings -> readString();
-						break;
-					case 'si':
-						if ($this -> SharedStrings -> nodeType == XMLReader::END_ELEMENT)
-						{
-							$this -> SSOpen = false;
-							$this -> SSForwarded = true;
-							break 2;
-						}
-						break;
-				}
-			}
-		}
-
-		if ($Value)
-		{
-			$this -> LastSharedStringValue = $Value;
-		}
-		return $Value;
-	}
-
-	/**
-	 * Formats the value according to the index
-	 *
-	 * @param string Cell value
-	 * @param int Format index
-	 *
-	 * @return string Formatted cell value
-	 */
-	private function FormatValue($Value, $Index)
-	{
-		if (!is_numeric($Value))
-		{
-			return $Value;
-		}
-
-		if (isset($this -> Styles[$Index]) && ($this -> Styles[$Index] !== false))
-		{
-			$Index = $this -> Styles[$Index];
-		}
-		else
-		{
-			return $Value;
-		}
-
-		// A special case for the "General" format
-		if ($Index == 0)
-		{
-			return $this -> GeneralFormat($Value);
-		}
-
-		$Format = array();
-
-		if (isset($this -> ParsedFormatCache[$Index]))
-		{
-			$Format = $this -> ParsedFormatCache[$Index];
-		}
-
-		if (!$Format)
-		{
-			$Format = array(
-				'Code' => false,
-				'Type' => false,
-				'Scale' => 1,
-				'Thousands' => false,
-				'Currency' => false
-			);
-
-			if (isset(self::$BuiltinFormats[$Index]))
-			{
-				$Format['Code'] = self::$BuiltinFormats[$Index];
-			}
-			elseif (isset($this -> Formats[$Index]))
-			{
-				$Format['Code'] = str_replace('"', '', $this -> Formats[$Index]);
-			}
-
-			// Format code found, now parsing the format
-			if ($Format['Code'])
-			{
-				$Sections = explode(';', $Format['Code']);
-				$Format['Code'] = $Sections[0];
-
-				switch (count($Sections))
-				{
-					case 2:
-						if ($Value < 0)
-						{
-							$Format['Code'] = $Sections[1];
-						}
-						$Value = abs($Value);
-						break;
-					case 3:
-					case 4:
-						if ($Value < 0)
-						{
-							$Format['Code'] = $Sections[1];
-						}
-						elseif ($Value == 0)
-						{
-							$Format['Code'] = $Sections[2];
-						}
-						$Value = abs($Value);
-						break;
-				}
-			}
-
-			// Stripping colors
-			$Format['Code'] = trim(preg_replace('/^\\[[a-zA-Z]+\\]/', '', $Format['Code']));
-
-			// Percentages
-			if (substr($Format['Code'], -1) == '%')
-			{
-				$Format['Type'] = 'Percentage';
-			}
-			elseif (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy]/i', $Format['Code']))
-			{
-				$Format['Type'] = 'DateTime';
-
-				$Format['Code'] = trim(preg_replace('/^(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $Format['Code']));
-				$Format['Code'] = strtolower($Format['Code']);
-
-				$Format['Code'] = strtr($Format['Code'], self::$DateReplacements['All']);
-				if (strpos($Format['Code'], 'A') === false)
-				{
-					$Format['Code'] = strtr($Format['Code'], self::$DateReplacements['24H']);
-				}
-				else
-				{
-					$Format['Code'] = strtr($Format['Code'], self::$DateReplacements['12H']);
-				}
-			}
-			elseif ($Format['Code'] == '[$EUR ]#,##0.00_-')
-			{
-				$Format['Type'] = 'Euro';
-			}
-			else
-			{
-				// Removing skipped characters
-				$Format['Code'] = preg_replace('/_./', '', $Format['Code']);
-				// Removing unnecessary escaping
-				$Format['Code'] = preg_replace("/\\\\/", '', $Format['Code']);
-				// Removing string quotes
-				$Format['Code'] = str_replace(array('"', '*'), '', $Format['Code']);
-				// Removing thousands separator
-				if (strpos($Format['Code'], '0,0') !== false || strpos($Format['Code'], '#,#') !== false)
-				{
-					$Format['Thousands'] = true;
-				}
-				$Format['Code'] = str_replace(array('0,0', '#,#'), array('00', '##'), $Format['Code']);
-
-				// Scaling (Commas indicate the power)
-				$Scale = 1;
-				$Matches = array();
-				if (preg_match('/(0|#)(,+)/', $Format['Code'], $Matches))
-				{
-					$Scale = pow(1000, strlen($Matches[2]));
-					// Removing the commas
-					$Format['Code'] = preg_replace(array('/0,+/', '/#,+/'), array('0', '#'), $Format['Code']);
-				}
-
-				$Format['Scale'] = $Scale;
-
-				if (preg_match('/#?.*\?\/\?/', $Format['Code']))
-				{
-					$Format['Type'] = 'Fraction';
-				}
-				else
-				{
-					$Format['Code'] = str_replace('#', '', $Format['Code']);
-
-					$Matches = array();
-					if (preg_match('/(0+)(\.?)(0*)/', preg_replace('/\[[^\]]+\]/', '', $Format['Code']), $Matches))
-					{
-						$Integer = $Matches[1];
-						$DecimalPoint = $Matches[2];
-						$Decimals = $Matches[3];
-
-						$Format['MinWidth'] = strlen($Integer) + strlen($DecimalPoint) + strlen($Decimals);
-						$Format['Decimals'] = $Decimals;
-						$Format['Precision'] = strlen($Format['Decimals']);
-						$Format['Pattern'] = '%0'.$Format['MinWidth'].'.'.$Format['Precision'].'f';
-					}
-				}
-
-				$Matches = array();
-				if (preg_match('/\[\$(.*)\]/u', $Format['Code'], $Matches))
-				{
-					$CurrFormat = $Matches[0];
-					$CurrCode = $Matches[1];
-					$CurrCode = explode('-', $CurrCode);
-					if ($CurrCode)
-					{
-						$CurrCode = $CurrCode[0];
-					}
-
-					if (!$CurrCode)
-					{
-						$CurrCode = self::$CurrencyCode;
-					}
-
-					$Format['Currency'] = $CurrCode;
-				}
-				$Format['Code'] = trim($Format['Code']);
-			}
-
-			$this -> ParsedFormatCache[$Index] = $Format;
-		}
-
-		// Applying format to value
-		if ($Format)
-		{
-			if ($Format['Code'] == '@')
-			{
-				return (string)$Value;
-			}
-			// Percentages
-			elseif ($Format['Type'] == 'Percentage')
-			{
-				if ($Format['Code'] === '0%')
-				{
-					$Value = round(100 * $Value, 0).'%';
-				}
-				else
-				{
-					$Value = sprintf('%.2f%%', round(100 * $Value, 2));
-				}
-			}
-			// Dates and times
-			elseif ($Format['Type'] == 'DateTime')
-			{
-				$Days = (int)$Value;
-				// Correcting for Feb 29, 1900
-				if ($Days > 60)
-				{
-					$Days--;
-				}
-
-				// At this point time is a fraction of a day
-				$Time = ($Value - (int)$Value);
-				$Seconds = 0;
-				if ($Time)
-				{
-					// Here time is converted to seconds
-					// Some loss of precision will occur
-					$Seconds = (int)($Time * 86400);
-				}
-
-				$Value = clone self::$BaseDate;
-				$Value -> add(new DateInterval('P'.$Days.'D'.($Seconds ? 'T'.$Seconds.'S' : '')));
-
-				if (!$this -> Options['ReturnDateTimeObjects'])
-				{
-					$Value = $Value -> format($Format['Code']);
-				}
-				else
-				{
-					// A DateTime object is returned
-				}
-			}
-			elseif ($Format['Type'] == 'Euro')
-			{
-				$Value = 'EUR '.sprintf('%1.2f', $Value);
-			}
-			else
-			{
-				// Fractional numbers
-				if ($Format['Type'] == 'Fraction' && ($Value != (int)$Value))
-				{
-					$Integer = floor(abs($Value));
-					$Decimal = fmod(abs($Value), 1);
-					// Removing the integer part and decimal point
-					$Decimal *= pow(10, strlen($Decimal) - 2);
-					$DecimalDivisor = pow(10, strlen($Decimal));
-
-					if (self::$RuntimeInfo['GMPSupported'])
-					{
-						$GCD = gmp_strval(gmp_gcd($Decimal, $DecimalDivisor));
-					}
-					else
-					{
-						$GCD = self::GCD($Decimal, $DecimalDivisor);
-					}
-
-					$AdjDecimal = $DecimalPart/$GCD;
-					$AdjDecimalDivisor = $DecimalDivisor/$GCD;
-
-					if (
-						strpos($Format['Code'], '0') !== false || 
-						strpos($Format['Code'], '#') !== false ||
-						substr($Format['Code'], 0, 3) == '? ?'
-					)
-					{
-						// The integer part is shown separately apart from the fraction
-						$Value = ($Value < 0 ? '-' : '').
-							$Integer ? $Integer.' ' : ''.
-							$AdjDecimal.'/'.
-							$AdjDecimalDivisor;
-					}
-					else
-					{
-						// The fraction includes the integer part
-						$AdjDecimal += $Integer * $AdjDecimalDivisor;
-						$Value = ($Value < 0 ? '-' : '').
-							$AdjDecimal.'/'.
-							$AdjDecimalDivisor;
-					}
-				}
-				else
-				{
-					// Scaling
-					$Value = $Value / $Format['Scale'];
-
-					if (!empty($Format['MinWidth']) && $Format['Decimals'])
-					{
-						if ($Format['Thousands'])
-						{
-							$Value = number_format($Value, $Format['Precision'],
-								self::$DecimalSeparator, self::$ThousandSeparator);
-							
-							$Value = preg_replace('/(0+)(\.?)(0*)/', $Value, $Format['Code']);
-						}
-						else{
-							if (preg_match('/[0#]E[+-]0/i', $Format['Code'])) {
-								//	Scientific format
-								$Value = sprintf('%5.2E', $Value);
-							}
-							else {
-								$Value = sprintf($Format['Pattern'], $Value);
-								$Value = preg_replace('/(0+)(\.?)(0*)/', $Value, $Format['Code']);
-							}
-						}
-					}
-				}
-
-				// Currency/Accounting
-				if ($Format['Currency'])
-				{
-					$Value = preg_replace('', $Format['Currency'], $Value);
-				}
-			}
-			
-		}
-
-		return $Value;
-	}
-
-	/**
-	 * Attempts to approximate Excel's "general" format.
-	 *
-	 * @param mixed Value
-	 *
-	 * @return mixed Result
-	 */
-	public function GeneralFormat($Value)
-	{
-		// Numeric format
-		if (is_numeric($Value))
-		{
-			$Value = (float)$Value;
-		}
-		return $Value;
-	}
-
-	// !Iterator interface methods
-	/** 
-	 * Rewind the Iterator to the first element.
-	 * Similar to the reset() function for arrays in PHP
-	 */ 
-	public function rewind()
-	{
-		// Removed the check whether $this -> Index == 0 otherwise ChangeSheet doesn't work properly
-
-		// If the worksheet was already iterated, XML file is reopened.
-		// Otherwise it should be at the beginning anyway
-		if ($this -> Worksheet instanceof XMLReader)
-		{
-			$this -> Worksheet -> close();
-		}
-		else
-		{
-			$this -> Worksheet = new XMLReader;
-		}
-
-		$this -> Worksheet -> open($this -> WorksheetPath);
-
-		$this -> Valid = true;
-		$this -> RowOpen = false;
-		$this -> CurrentRow = false;
-		$this -> Index = 0;
-	}
-
-	/**
-	 * Return the current element.
-	 * Similar to the current() function for arrays in PHP
-	 *
-	 * @return mixed current element from the collection
-	 */
-	public function current()
-	{
-		if ($this -> Index == 0 && $this -> CurrentRow === false)
-		{
-			$this -> rewind();
-			$this -> next();
-			$this -> Index--;
-		}
-		return $this -> CurrentRow;
-	}
-
-	/** 
-	 * Move forward to next element. 
-	 * Similar to the next() function for arrays in PHP 
-	 */ 
-	public function next()
-	{
-		$this -> Index++;
-
-		$this -> CurrentRow = array();
-
-		if (!$this -> RowOpen)
-		{
-			while ($this -> Valid = $this -> Worksheet -> read())
-			{
-				if ($this -> Worksheet -> name == 'row')
-				{
-					// Getting the row spanning area (stored as e.g., 1:12)
-					// so that the last cells will be present, even if empty
-					$RowSpans = $this -> Worksheet -> getAttribute('spans');
-					if ($RowSpans)
-					{
-						$RowSpans = explode(':', $RowSpans);
-						$CurrentRowColumnCount = $RowSpans[1];
-					}
-					else
-					{
-						$CurrentRowColumnCount = 0;
-					}
-
-					if ($CurrentRowColumnCount > 0)
-					{
-						$this -> CurrentRow = array_fill(0, $CurrentRowColumnCount, '');
-					}
-
-					$this -> RowOpen = true;
-					break;
-				}
-			}
-		}
-
-		// Reading the necessary row, if found
-		if ($this -> RowOpen)
-		{
-			// These two are needed to control for empty cells
-			$MaxIndex = 0;
-			$CellCount = 0;
-
-			$CellHasSharedString = false;
-
-			while ($this -> Valid = $this -> Worksheet -> read())
-			{
-				switch ($this -> Worksheet -> name)
-				{
-					// End of row
-					case 'row':
-						if ($this -> Worksheet -> nodeType == XMLReader::END_ELEMENT)
-						{
-							$this -> RowOpen = false;
-							break 2;
-						}
-						break;
-					// Cell
-					case 'c':
-						// If it is a closing tag, skip it
-						if ($this -> Worksheet -> nodeType == XMLReader::END_ELEMENT)
-						{
-							continue;
-						}
-
-						$StyleId = (int)$this -> Worksheet -> getAttribute('s');
-
-						// Get the index of the cell
-						$Index = $this -> Worksheet -> getAttribute('r');
-						$Letter = preg_replace('{[^[:alpha:]]}S', '', $Index);
-						$Index = self::IndexFromColumnLetter($Letter);
-
-						// Determine cell type
-						if ($this -> Worksheet -> getAttribute('t') == self::CELL_TYPE_SHARED_STR)
-						{
-							$CellHasSharedString = true;
-						}
-						else
-						{
-							$CellHasSharedString = false;
-						}
-
-						$this -> CurrentRow[$Index] = '';
-
-						$CellCount++;
-						if ($Index > $MaxIndex)
-						{
-							$MaxIndex = $Index;
-						}
-
-						break;
-					// Cell value
-					case 'v':
-					case 'is':
-						if ($this -> Worksheet -> nodeType == XMLReader::END_ELEMENT)
-						{
-							continue;
-						}
-
-						$Value = $this -> Worksheet -> readString();
-
-						if ($CellHasSharedString)
-						{
-							$Value = $this -> GetSharedString($Value);
-						}
-
-						// Format value if necessary
-						if ($Value !== '' && $StyleId && isset($this -> Styles[$StyleId]))
-						{
-							$Value = $this -> FormatValue($Value, $StyleId);
-						}
-						elseif ($Value)
-						{
-							$Value = $this -> GeneralFormat($Value);
-						}
-
-						$this -> CurrentRow[$Index] = $Value;
-						break;
-				}
-			}
-
-			// Adding empty cells, if necessary
-			// Only empty cells inbetween and on the left side are added
-			if ($MaxIndex + 1 > $CellCount)
-			{
-				$this -> CurrentRow = $this -> CurrentRow + array_fill(0, $MaxIndex + 1, '');
-				ksort($this -> CurrentRow);
-			}
-		}
-
-		return $this -> CurrentRow;
-	}
-
-	/** 
-	 * Return the identifying key of the current element.
-	 * Similar to the key() function for arrays in PHP
-	 *
-	 * @return mixed either an integer or a string
-	 */ 
-	public function key()
-	{
-		return $this -> Index;
-	}
-
-	/** 
-	 * Check if there is a current element after calls to rewind() or next().
-	 * Used to check if we've iterated to the end of the collection
-	 *
-	 * @return boolean FALSE if there's nothing more to iterate over
-	 */ 
-	public function valid()
-	{
-		return $this -> Valid;
-	}
-
-	// !Countable interface method
-	/**
-	 * Ostensibly should return the count of the contained items but this just returns the number
-	 * of rows read so far. It's not really correct but at least coherent.
-	 */
-	public function count()
-	{
-		if(is_null($this->rowCount)){
-			$total = 0;
-			while($this -> Worksheet -> read()){
-				if ($this -> Worksheet -> name == 'row' && $this -> Worksheet -> nodeType != XMLReader::END_ELEMENT){
-					$total++;
-				}
-			}
-			
-			$this->rowCount = $total;
-		}
-		
-		
-		return $this->rowCount;
-	}
-
-	/**
-	 * Takes the column letter and converts it to a numerical index (0-based)
-	 *
-	 * @param string Letter(s) to convert
-	 *
-	 * @return mixed Numeric index (0-based) or boolean false if it cannot be calculated
-	 */
-	public static function IndexFromColumnLetter($Letter)
-	{
-		$Powers = array();
-
-		$Letter = strtoupper($Letter);
-
-		$Result = 0;
-		for ($i = strlen($Letter) - 1, $j = 0; $i >= 0; $i--, $j++)
-		{
-			$Ord = ord($Letter[$i]) - 64;
-			if ($Ord > 26)
-			{
-				// Something is very, very wrong
-				return false;
-			}
-			$Result += $Ord * pow(26, $j);
-		}
-		return $Result - 1;
-	}
-
-	/**
-	 * Helper function for greatest common divisor calculation in case GMP extension is not enabled
-	 *
-	 * @param int Number #1
-	 * @param int Number #2
-	 *
-	 * @param int Greatest common divisor
-	 */
-	public static function GCD($A, $B)
-	{
-		$A = abs($A);
-		$B = abs($B);
-		if ($A + $B == 0)
-		{
-			return 0;
-		}
-		else
-		{
-			$C = 1;
-
-			while ($A > 0)
-			{
-				$C = $A;
-				$A = $B % $A;
-				$B = $C;
-			}
-
-			return $C;
-		}
-	}
+    const CELL_TYPE_BOOL = 'b';
+    const CELL_TYPE_NUMBER = 'n';
+    const CELL_TYPE_ERROR = 'e';
+    const CELL_TYPE_SHARED_STR = 's';
+    const CELL_TYPE_STR = 'str';
+    const CELL_TYPE_INLINE_STR = 'inlineStr';
+    /**
+     * Number of shared strings that can be reasonably cached, i.e., that aren't read from file but stored in memory.
+     *    If the total number of shared strings is higher than this, caching is not used.
+     *    If this value is null, shared strings are cached regardless of amount.
+     *    With large shared string caches there are huge performance gains, however a lot of memory could be used which
+     *    can be a problem, especially on shared hosting.
+     */
+    const SHARED_STRING_CACHE_LIMIT = 50000;
+
+    private $Options = array(
+        'TempDir' => '',
+        'ReturnDateTimeObjects' => false
+    );
+
+    private static $RuntimeInfo = array('GMPSupported' => false);
+
+    private $Valid = false;
+
+    /**
+     * @var SpreadsheetReader_* Handle for the reader object
+     */
+    private $Handle = false;
+
+    // Worksheet file
+    /**
+     * @var string Path to the worksheet XML file
+     */
+    private $WorksheetPath = false;
+
+    /**
+     * @var XMLReader XML reader object for the worksheet XML file
+     */
+    private $Worksheet = false;
+
+    // Shared strings file
+    /**
+     * @var string Path to shared strings XML file
+     */
+    private $SharedStringsPath = false;
+
+    /**
+     * @var XMLReader XML reader object for the shared strings XML file
+     */
+    private $SharedStrings = false;
+
+    /**
+     * @var array Shared strings cache, if the number of shared strings is low enough
+     */
+    private $SharedStringCache = array();
+
+    // Workbook data
+    /**
+     * @var SimpleXMLElement XML object for the workbook XML file
+     */
+    private $WorkbookXML = false;
+
+    // Style data
+    /**
+     * @var SimpleXMLElement XML object for the styles XML file
+     */
+    private $StylesXML = false;
+
+    /**
+     * @var array Container for cell value style data
+     */
+    private $Styles = array();
+
+    private $TempDir = '';
+
+    private $TempFiles = array();
+
+    private $CurrentRow = false;
+
+    private $rowCount = null;
+
+    // Runtime parsing data
+    /**
+     * @var int Current row in the file
+     */
+    private $Index = 0;
+
+    /**
+     * @var array Data about separate sheets in the file
+     */
+    private $Sheets = false;
+
+    private $SharedStringCount = 0;
+
+    private $SharedStringIndex = 0;
+
+    private $LastSharedStringValue = null;
+
+    private $RowOpen = false;
+
+    private $SSOpen = false;
+
+    private $SSForwarded = false;
+
+    private static $BuiltinFormats = array(
+        0 => '',
+        1 => '0',
+        2 => '0.00',
+        3 => '#,##0',
+        4 => '#,##0.00',
+        9 => '0%',
+        10 => '0.00%',
+        11 => '0.00E+00',
+        12 => '# ?/?',
+        13 => '# ??/??',
+        14 => 'yyyy/m/d',
+        15 => 'd-mmm-yy',
+        16 => 'd-mmm',
+        17 => 'mmm-yy',
+        18 => 'h:mm AM/PM',
+        19 => 'h:mm:ss AM/PM',
+        20 => 'h:mm',
+        21 => 'h:mm:ss',
+        22 => 'yyyy/m/d h:mm',
+        31 => 'yyyy年m月d日',
+        32 => 'h时mmi分',
+        33 => 'h时mmi分ss秒',
+        37 => '#,##0 ;(#,##0)',
+        38 => '#,##0 ;[Red](#,##0)',
+        39 => '#,##0.00;(#,##0.00)',
+        40 => '#,##0.00;[Red](#,##0.00)',
+        44 => '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)',
+        45 => 'mm:ss',
+        46 => '[h]:mm:ss',
+        47 => 'mm:ss.0',
+        48 => '##0.0E+0',
+        49 => '@',
+        55 => 'AM/PM h时mmi分',
+        56 => 'AM/PM h时mmi分ss秒',
+        58 => 'm月d日', // CHT & CHS
+        27 => 'yyyy年m月',
+        30 => 'm/d/yy',
+        36 => '[$-404]e/m/d',
+        50 => '[$-404]e/m/d',
+        57 => '[$-404]e/m/d', // THA
+        59 => 't0',
+        60 => 't0.00',
+        61 => 't#,##0',
+        62 => 't#,##0.00',
+        67 => 't0%',
+        68 => 't0.00%',
+        69 => 't# ?/?',
+        70 => 't# ??/??'
+    );
+
+    private $Formats = array();
+
+    private static $DateReplacements = array(
+        'All' => array(
+            '\\' => '',
+            'am/pm' => 'A',
+            'e' => 'Y',
+            'yyyy' => 'Y',
+            'yy' => 'y',
+            'mmmmm' => 'M',
+            'mmmm' => 'F',
+            'mmm' => 'M',
+            ':mm' => ':i',
+            'mmi' => 'i',
+            'mm' => 'm',
+            'm' => 'n',
+            'dddd' => 'l',
+            'ddd' => 'D',
+            'dd' => 'd',
+            'd' => 'j',
+            'ss' => 's',
+            '.s' => ''
+        ),
+        '24H' => array(
+            'hh' => 'H',
+            'h' => 'G'
+        ),
+        '12H' => array(
+            'hh' => 'h', 'h' => 'g'
+        )
+    );
+
+    private static $BaseDate = false;
+
+    private static $DecimalSeparator = '.';
+
+    private static $ThousandSeparator = ',';
+
+    private static $CurrencyCode = '';
+
+    /**
+     * @var array Cache for already processed format strings
+     */
+    private $ParsedFormatCache = array();
+
+    /**
+     * @param string $Filepath Path to file
+     * @param array $Options  Options:
+     *                        TempDir => string Temporary directory path
+     *                        ReturnDateTimeObjects => bool True => dates and times will be returned as PHP DateTime objects,
+     *                        false => as strings
+     * @throws Exception
+     */
+    public function __construct($Filepath, array $Options = null) {
+        if (! is_readable($Filepath)) {
+            throw new Exception('SpreadsheetReader_XLSX: File not readable (' . $Filepath . ')');
+        }
+
+        $this->TempDir = isset($Options['TempDir']) && is_writable($Options['TempDir']) ? $Options['TempDir'] : sys_get_temp_dir();
+        $this->TempDir = rtrim($this->TempDir, DIRECTORY_SEPARATOR);
+        $this->TempDir = $this->TempDir . DIRECTORY_SEPARATOR . uniqid() . DIRECTORY_SEPARATOR;
+
+        $Zip = new ZipArchive;
+        $Status = $Zip->open($Filepath);
+
+        if ($Status !== true) {
+            throw new Exception('SpreadsheetReader_XLSX: File not readable (' . $Filepath . ') (Error ' . $Status . ')');
+        }
+
+        // Getting the general workbook information
+        if ($Zip->locateName('xl/workbook.xml') !== false) {
+            $this->WorkbookXML = new SimpleXMLElement($Zip->getFromName('xl/workbook.xml'));
+        }
+
+        // Extracting the XMLs from the XLSX zip file
+        if ($Zip->locateName('xl/sharedStrings.xml') !== false) {
+            $this->SharedStringsPath = $this->TempDir . 'xl' . DIRECTORY_SEPARATOR . 'sharedStrings.xml';
+            $Zip->extractTo($this->TempDir, 'xl/sharedStrings.xml');
+            $this->TempFiles[] = $this->TempDir . 'xl' . DIRECTORY_SEPARATOR . 'sharedStrings.xml';
+
+            if (is_readable($this->SharedStringsPath)) {
+                $this->SharedStrings = new XMLReader;
+                $this->SharedStrings->open($this->SharedStringsPath);
+                $this->PrepareSharedStringCache();
+            }
+        }
+
+        $Sheets = $this->Sheets();
+
+        foreach ($this->Sheets as $Index => $Name) {
+            if ($Zip->locateName('xl/worksheets/sheet' . $Index . '.xml') !== false) {
+                $Zip->extractTo($this->TempDir, 'xl/worksheets/sheet' . $Index . '.xml');
+                $this->TempFiles[] = $this->TempDir . 'xl' . DIRECTORY_SEPARATOR . 'worksheets' . DIRECTORY_SEPARATOR . 'sheet' . $Index . '.xml';
+            }
+        }
+
+        $this->ChangeSheet(0);
+
+        // If worksheet is present and is OK, parse the styles already
+        if ($Zip->locateName('xl/styles.xml') !== false) {
+            $this->StylesXML = new SimpleXMLElement($Zip->getFromName('xl/styles.xml'));
+            if ($this->StylesXML && $this->StylesXML->cellXfs && $this->StylesXML->cellXfs->xf) {
+                foreach ($this->StylesXML->cellXfs->xf as $Index => $XF) {
+                    // Format #0 is a special case - it is the "General" format that is applied regardless of applyNumberFormat
+                    if ($XF->attributes()->applyNumberFormat || (0 == (int)$XF->attributes()->numFmtId)) {
+                        $FormatId = (int)$XF->attributes()->numFmtId;
+                        // If format ID >= 164, it is a custom format and should be read from styleSheet\numFmts
+                        $this->Styles[] = $FormatId;
+                    } else {
+                        // 0 for "General" format
+                        $this->Styles[] = 0;
+                    }
+                }
+            }
+
+            if ($this->StylesXML->numFmts && $this->StylesXML->numFmts->numFmt) {
+                foreach ($this->StylesXML->numFmts->numFmt as $Index => $NumFmt) {
+                    $this->Formats[(int)$NumFmt->attributes()->numFmtId] = (string)$NumFmt->attributes()->formatCode;
+                }
+            }
+
+            unset($this->StylesXML);
+        }
+
+        $Zip->close();
+
+        // Setting base date
+        if (! self::$BaseDate) {
+            self::$BaseDate = new DateTime;
+            self::$BaseDate->setTimezone(new DateTimeZone('UTC'));
+            self::$BaseDate->setDate(1900, 1, 0);
+            self::$BaseDate->setTime(0, 0, 0);
+        }
+
+        // Decimal and thousand separators
+        if (! self::$DecimalSeparator && ! self::$ThousandSeparator && ! self::$CurrencyCode) {
+            $Locale = localeconv();
+            self::$DecimalSeparator = $Locale['decimal_point'];
+            self::$ThousandSeparator = $Locale['thousands_sep'];
+            self::$CurrencyCode = $Locale['int_curr_symbol'];
+        }
+
+        if (function_exists('gmp_gcd')) {
+            self::$RuntimeInfo['GMPSupported'] = true;
+        }
+    }
+
+    /**
+     * Destructor, destroys all that remains (closes and deletes temp files)
+     */
+    public function __destruct() {
+        foreach ($this->TempFiles as $TempFile) {
+            @unlink($TempFile);
+        }
+
+        // Better safe than sorry - shouldn't try deleting '.' or '/', or '..'.
+        if (strlen($this->TempDir) > 2) {
+            @rmdir($this->TempDir . 'xl' . DIRECTORY_SEPARATOR . 'worksheets');
+            @rmdir($this->TempDir . 'xl');
+            @rmdir($this->TempDir);
+        }
+
+        if ($this->Worksheet && $this->Worksheet instanceof XMLReader) {
+            $this->Worksheet->close();
+            unset($this->Worksheet);
+        }
+        unset($this->WorksheetPath);
+
+        if ($this->SharedStrings && $this->SharedStrings instanceof XMLReader) {
+            $this->SharedStrings->close();
+            unset($this->SharedStrings);
+        }
+        unset($this->SharedStringsPath);
+
+        if (isset($this->StylesXML)) {
+            unset($this->StylesXML);
+        }
+        if ($this->WorkbookXML) {
+            unset($this->WorkbookXML);
+        }
+    }
+
+    /**
+     * Retrieves an array with information about sheets in the current file
+     * @return array List of sheets (key is sheet index, value is name)
+     */
+    public function Sheets() {
+        if ($this->Sheets === false) {
+            $this->Sheets = array();
+            foreach ($this->WorkbookXML->sheets->sheet as $Index => $Sheet) {
+                $Attributes = $Sheet->attributes('r', true);
+                foreach ($Attributes as $Name => $Value) {
+                    if ($Name == 'id') {
+                        $SheetID = (int)str_replace('rId', '', (string)$Value);
+                        break;
+                    }
+                }
+                $this->Sheets[$SheetID] = (string)$Sheet['name'];
+            }
+            ksort($this->Sheets);
+        }
+
+        return array_values($this->Sheets);
+    }
+
+    /**
+     * Changes the current sheet in the file to another
+     *
+     * @param int Sheet index
+     *
+     * @return bool True if sheet was successfully changed, false otherwise.
+     */
+    public function ChangeSheet($Index) {
+        $RealSheetIndex = false;
+        $Sheets = $this->Sheets();
+        if (isset($Sheets[$Index])) {
+            $SheetIndexes = array_keys($this->Sheets);
+            $RealSheetIndex = $SheetIndexes[$Index];
+        }
+
+        $TempWorksheetPath = $this->TempDir . 'xl/worksheets/sheet' . $RealSheetIndex . '.xml';
+        if ($RealSheetIndex !== false && is_readable($TempWorksheetPath)) {
+            $this->WorksheetPath = $TempWorksheetPath;
+            $this->rewind();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Creating shared string cache if the number of shared strings is acceptably low (or there is no limit on the
+     * amount
+     */
+    private function PrepareSharedStringCache() {
+        while ($this->SharedStrings->read()) {
+            if ($this->SharedStrings->name == 'sst') {
+                $this->SharedStringCount = $this->SharedStrings->getAttribute('count');
+                break;
+            }
+        }
+
+        if (! $this->SharedStringCount || (self::SHARED_STRING_CACHE_LIMIT < $this->SharedStringCount && self::SHARED_STRING_CACHE_LIMIT !== null)) {
+            return false;
+        }
+
+        $CacheIndex = 0;
+        $CacheValue = '';
+        while ($this->SharedStrings->read()) {
+            switch ($this->SharedStrings->name) {
+                case 'si':
+                    if ($this->SharedStrings->nodeType == XMLReader::END_ELEMENT) {
+                        $this->SharedStringCache[$CacheIndex] = $CacheValue;
+                        $CacheIndex++;
+                        $CacheValue = '';
+                    }
+                    break;
+                case 't':
+                    if ($this->SharedStrings->nodeType == XMLReader::END_ELEMENT) {
+                        continue;
+                    }
+                    $CacheValue .= $this->SharedStrings->readString();
+                    break;
+            }
+        }
+        $this->SharedStrings->close();
+
+        return true;
+    }
+
+    /**
+     * Retrieves a shared string value by its index
+     *
+     * @param int Shared string index
+     *
+     * @return string Value
+     */
+    private function GetSharedString($Index) {
+        if ((self::SHARED_STRING_CACHE_LIMIT === null || self::SHARED_STRING_CACHE_LIMIT > 0) && ! empty($this->SharedStringCache)) {
+            if (isset($this->SharedStringCache[$Index])) {
+                return $this->SharedStringCache[$Index];
+            } else {
+                return '';
+            }
+        }
+
+        // If the desired index is before the current, rewind the XML
+        if ($this->SharedStringIndex > $Index) {
+            $this->SSOpen = false;
+            $this->SharedStrings->close();
+            $this->SharedStrings->open($this->SharedStringsPath);
+            $this->SharedStringIndex = 0;
+            $this->LastSharedStringValue = null;
+            $this->SSForwarded = false;
+        }
+
+        // Finding the unique string count (if not already read)
+        if ($this->SharedStringIndex == 0 && ! $this->SharedStringCount) {
+            while ($this->SharedStrings->read()) {
+                if ($this->SharedStrings->name == 'sst') {
+                    $this->SharedStringCount = $this->SharedStrings->getAttribute('uniqueCount');
+                    break;
+                }
+            }
+        }
+
+        // If index of the desired string is larger than possible, don't even bother.
+        if ($this->SharedStringCount && ($Index >= $this->SharedStringCount)) {
+            return '';
+        }
+
+        // If an index with the same value as the last already fetched is requested
+        // (any further traversing the tree would get us further away from the node)
+        if (($Index == $this->SharedStringIndex) && ($this->LastSharedStringValue !== null)) {
+            return $this->LastSharedStringValue;
+        }
+
+        // Find the correct <si> node with the desired index
+        while ($this->SharedStringIndex <= $Index) {
+            // SSForwarded is set further to avoid double reading in case nodes are skipped.
+            if ($this->SSForwarded) {
+                $this->SSForwarded = false;
+            } else {
+                $ReadStatus = $this->SharedStrings->read();
+                if (! $ReadStatus) {
+                    break;
+                }
+            }
+
+            if ($this->SharedStrings->name == 'si') {
+                if ($this->SharedStrings->nodeType == XMLReader::END_ELEMENT) {
+                    $this->SSOpen = false;
+                    $this->SharedStringIndex++;
+                } else {
+                    $this->SSOpen = true;
+                    if ($this->SharedStringIndex < $Index) {
+                        $this->SSOpen = false;
+                        $this->SharedStrings->next('si');
+                        $this->SSForwarded = true;
+                        $this->SharedStringIndex++;
+                        continue;
+                    } else {
+                        break;
+                    }
+                }
+            }
+        }
+
+        $Value = '';
+
+        // Extract the value from the shared string
+        if ($this->SSOpen && ($this->SharedStringIndex == $Index)) {
+            while ($this->SharedStrings->read()) {
+                switch ($this->SharedStrings->name) {
+                    case 't':
+                        if ($this->SharedStrings->nodeType == XMLReader::END_ELEMENT) {
+                            continue;
+                        }
+                        $Value .= $this->SharedStrings->readString();
+                        break;
+                    case 'si':
+                        if ($this->SharedStrings->nodeType == XMLReader::END_ELEMENT) {
+                            $this->SSOpen = false;
+                            $this->SSForwarded = true;
+                            break 2;
+                        }
+                        break;
+                }
+            }
+        }
+
+        if ($Value) {
+            $this->LastSharedStringValue = $Value;
+        }
+
+        return $Value;
+    }
+
+    /**
+     * Formats the value according to the index
+     *
+     * @param string Cell value
+     * @param int    Format index
+     *
+     * @return string Formatted cell value
+     */
+    private function FormatValue($Value, $Index) {
+        if (! is_numeric($Value)) {
+            return $Value;
+        }
+
+        if (isset($this->Styles[$Index]) && ($this->Styles[$Index] !== false)) {
+            $Index = $this->Styles[$Index];
+        } else {
+            return $Value;
+        }
+
+        // A special case for the "General" format
+        if ($Index == 0) {
+            return $this->GeneralFormat($Value);
+        }
+
+        $Format = array();
+
+        if (isset($this->ParsedFormatCache[$Index])) {
+            $Format = $this->ParsedFormatCache[$Index];
+        }
+        if (! $Format) {
+            $Format = array('Code' => false, 'Type' => false, 'Scale' => 1, 'Thousands' => false, 'Currency' => false);
+
+            if (isset(self::$BuiltinFormats[$Index])) {
+                $Format['Code'] = self::$BuiltinFormats[$Index];
+            } elseif (isset($this->Formats[$Index])) {
+                $Format['Code'] = str_replace('"', '', $this->Formats[$Index]);
+            }
+
+            // Format code found, now parsing the format
+            if ($Format['Code']) {
+                $Sections = explode(';', $Format['Code']);
+                $Format['Code'] = $Sections[0];
+                switch (count($Sections)) {
+                    case 2:
+                        if ($Value < 0) {
+                            $Format['Code'] = $Sections[1];
+                        }
+                        $Value = abs($Value);
+                        break;
+                    case 3:
+                    case 4:
+                        if ($Value < 0) {
+                            $Format['Code'] = $Sections[1];
+                        } elseif ($Value == 0) {
+                            $Format['Code'] = $Sections[2];
+                        }
+                        $Value = abs($Value);
+                        break;
+                }
+            }
+
+            // Stripping colors
+            $Format['Code'] = trim(preg_replace('/^\\[[a-zA-Z]+\\]/', '', $Format['Code']));
+
+            // Percentages
+            if (substr($Format['Code'], -1) == '%') {
+                $Format['Type'] = 'Percentage';
+            } elseif (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy]/i', $Format['Code'])) {
+                $Format['Type'] = 'DateTime';
+                $Format['Code'] = trim(preg_replace('/^(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $Format['Code']));
+                $Format['Code'] = strtolower($Format['Code']);
+                $Format['Code'] = strtr($Format['Code'], self::$DateReplacements['All']);
+                if (strpos($Format['Code'], 'A') === false) {
+                    $Format['Code'] = strtr($Format['Code'], self::$DateReplacements['24H']);
+                } else {
+                    $Format['Code'] = strtr($Format['Code'], self::$DateReplacements['12H']);
+                }
+            } elseif ($Format['Code'] == '[$EUR ]#,##0.00_-') {
+                $Format['Type'] = 'Euro';
+            } else {
+                // Removing skipped characters
+                $Format['Code'] = preg_replace('/_./', '', $Format['Code']);
+                // Removing unnecessary escaping
+                $Format['Code'] = preg_replace("/\\\\/", '', $Format['Code']);
+                // Removing string quotes
+                $Format['Code'] = str_replace(array('"', '*'), '', $Format['Code']);
+                // Removing thousands separator
+                if (strpos($Format['Code'], '0,0') !== false || strpos($Format['Code'], '#,#') !== false) {
+                    $Format['Thousands'] = true;
+                }
+                $Format['Code'] = str_replace(array('0,0', '#,#'), array('00', '##'), $Format['Code']);
+                // Scaling (Commas indicate the power)
+                $Scale = 1;
+                $Matches = array();
+                if (preg_match('/(0|#)(,+)/', $Format['Code'], $Matches)) {
+                    $Scale = pow(1000, strlen($Matches[2]));
+                    // Removing the commas
+                    $Format['Code'] = preg_replace(array('/0,+/', '/#,+/'), array('0', '#'), $Format['Code']);
+                }
+                $Format['Scale'] = $Scale;
+                if (preg_match('/#?.*\?\/\?/', $Format['Code'])) {
+                    $Format['Type'] = 'Fraction';
+                } else {
+                    $Format['Code'] = str_replace('#', '', $Format['Code']);
+                    $Matches = array();
+                    if (preg_match('/(0+)(\.?)(0*)/', preg_replace('/\[[^\]]+\]/', '', $Format['Code']), $Matches)) {
+                        $Integer = $Matches[1];
+                        $DecimalPoint = $Matches[2];
+                        $Decimals = $Matches[3];
+                        $Format['MinWidth'] = strlen($Integer) + strlen($DecimalPoint) + strlen($Decimals);
+                        $Format['Decimals'] = $Decimals;
+                        $Format['Precision'] = strlen($Format['Decimals']);
+                        $Format['Pattern'] = '%0' . $Format['MinWidth'] . '.' . $Format['Precision'] . 'f';
+                    }
+                }
+                $Matches = array();
+                if (preg_match('/\[\$(.*)\]/u', $Format['Code'], $Matches)) {
+                    $CurrFormat = $Matches[0];
+                    $CurrCode = $Matches[1];
+                    $CurrCode = explode('-', $CurrCode);
+                    if ($CurrCode) {
+                        $CurrCode = $CurrCode[0];
+                    }
+                    if (! $CurrCode) {
+                        $CurrCode = self::$CurrencyCode;
+                    }
+                    $Format['Currency'] = $CurrCode;
+                }
+                $Format['Code'] = trim($Format['Code']);
+            }
+            $this->ParsedFormatCache[$Index] = $Format;
+        }
+        // Applying format to value
+        if ($Format) {
+            if ($Format['Code'] == '@') {
+                return (string)$Value;
+            } // Percentages
+            elseif ($Format['Type'] == 'Percentage') {
+                if ($Format['Code'] === '0%') {
+                    $Value = round(100*$Value, 0) . '%';
+                } else {
+                    $Value = sprintf('%.2f%%', round(100*$Value, 2));
+                }
+            } // Dates and times
+            elseif ($Format['Type'] == 'DateTime') {
+                $Days = (int)$Value;
+                // Correcting for Feb 29, 1900
+                if ($Days > 60) {
+                    $Days--;
+                }
+                // At this point time is a fraction of a day
+                $Time = ($Value - (int)$Value);
+                $Seconds = 0;
+                if ($Time) {
+                    // Here time is converted to seconds
+                    // Some loss of precision will occur
+                    $Seconds = (int)($Time*86400);
+                }
+                $Value = clone self::$BaseDate;
+                $Value->add(new DateInterval('P' . $Days . 'D' . ($Seconds ? 'T' . $Seconds . 'S' : '')));
+                if (! $this->Options['ReturnDateTimeObjects']) {
+                    $Value = $Value->format($Format['Code']);
+                } else {
+                    // A DateTime object is returned
+                }
+            } elseif ($Format['Type'] == 'Euro') {
+                $Value = 'EUR ' . sprintf('%1.2f', $Value);
+            } else {
+                // Fractional numbers
+                if ($Format['Type'] == 'Fraction' && ($Value != (int)$Value)) {
+                    $Integer = floor(abs($Value));
+                    $Decimal = fmod(abs($Value), 1);
+                    // Removing the integer part and decimal point
+                    $Decimal *= pow(10, strlen($Decimal) - 2);
+                    $DecimalDivisor = pow(10, strlen($Decimal));
+                    if (self::$RuntimeInfo['GMPSupported']) {
+                        $GCD = gmp_strval(gmp_gcd($Decimal, $DecimalDivisor));
+                    } else {
+                        $GCD = self::GCD($Decimal, $DecimalDivisor);
+                    }
+                    $AdjDecimal = $Decimal/$GCD;
+                    $AdjDecimalDivisor = $DecimalDivisor/$GCD;
+                    if (strpos($Format['Code'], '0') !== false || strpos($Format['Code'], '#') !== false || substr($Format['Code'], 0, 3) == '? ?') {
+                        // The integer part is shown separately apart from the fraction
+                        $Value = ($Value < 0 ? '-' : '') . $Integer ? $Integer . ' ' : '' . $AdjDecimal . '/' . $AdjDecimalDivisor;
+                    } else {
+                        // The fraction includes the integer part
+                        $AdjDecimal += $Integer*$AdjDecimalDivisor;
+                        $Value = ($Value < 0 ? '-' : '') . $AdjDecimal . '/' . $AdjDecimalDivisor;
+                    }
+                } else {
+                    // Scaling
+                    $Value = $Value/$Format['Scale'];
+                    if (! empty($Format['MinWidth']) && $Format['Decimals']) {
+                        if ($Format['Thousands']) {
+                            $Value = number_format($Value, $Format['Precision'], self::$DecimalSeparator, self::$ThousandSeparator);
+                            $Value = preg_replace('/(0+)(\.?)(0*)/', $Value, $Format['Code']);
+                        } else {
+                            if (preg_match('/[0#]E[+-]0/i', $Format['Code'])) {
+                                //	Scientific format
+                                $Value = sprintf('%5.2E', $Value);
+                            } else {
+                                $Value = sprintf($Format['Pattern'], $Value);
+                                $Value = preg_replace('/(0+)(\.?)(0*)/', $Value, $Format['Code']);
+                            }
+                        }
+                    }
+                }
+                // Currency/Accounting
+                if ($Format['Currency']) {
+                    $Value = preg_replace('', $Format['Currency'], $Value);
+                }
+            }
+        }
+
+        return $Value;
+    }
+
+    /**
+     * Attempts to approximate Excel's "general" format.
+     *
+     * @param mixed Value
+     *
+     * @return mixed Result
+     */
+    public function GeneralFormat($Value) {
+        // Numeric format
+        if (is_numeric($Value)) {
+            $Value = (float)$Value;
+        }
+
+        return $Value;
+    }
+
+    // !Iterator interface methods
+    /**
+     * Rewind the Iterator to the first element.
+     * Similar to the reset() function for arrays in PHP
+     */
+    public function rewind() {
+        // Removed the check whether $this -> Index == 0 otherwise ChangeSheet doesn't work properly
+        // If the worksheet was already iterated, XML file is reopened.
+        // Otherwise it should be at the beginning anyway
+        if ($this->Worksheet instanceof XMLReader) {
+            $this->Worksheet->close();
+        } else {
+            $this->Worksheet = new XMLReader;
+        }
+
+        $this->Worksheet->open($this->WorksheetPath);
+
+        $this->Valid = true;
+        $this->RowOpen = false;
+        $this->CurrentRow = false;
+        $this->Index = 0;
+    }
+
+    /**
+     * Return the current element.
+     * Similar to the current() function for arrays in PHP
+     * @return mixed current element from the collection
+     */
+    public function current() {
+        if ($this->Index == 0 && $this->CurrentRow === false) {
+            $this->rewind();
+            $this->next();
+            $this->Index--;
+        }
+
+        return $this->CurrentRow;
+    }
+
+    /**
+     * Move forward to next element.
+     * Similar to the next() function for arrays in PHP
+     */
+    public function next() {
+        $this->Index++;
+        $this->CurrentRow = array();
+        if (! $this->RowOpen) {
+            while ($this->Valid = $this->Worksheet->read()) {
+                if ($this->Worksheet->name == 'row') {
+                    // Getting the row spanning area (stored as e.g., 1:12)
+                    // so that the last cells will be present, even if empty
+                    $RowSpans = $this->Worksheet->getAttribute('spans');
+                    if ($RowSpans) {
+                        $RowSpans = explode(':', $RowSpans);
+                        $CurrentRowColumnCount = $RowSpans[1];
+                    } else {
+                        $CurrentRowColumnCount = 0;
+                    }
+
+                    if ($CurrentRowColumnCount > 0) {
+                        $this->CurrentRow = array_fill(0, $CurrentRowColumnCount, '');
+                    }
+
+                    $this->RowOpen = true;
+                    break;
+                }
+            }
+        }
+        // Reading the necessary row, if found
+        if ($this->RowOpen) {
+            // These two are needed to control for empty cells
+            $MaxIndex = 0;
+            $CellCount = 0;
+
+            $CellHasSharedString = false;
+
+            while ($this->Valid = $this->Worksheet->read()) {
+                switch ($this->Worksheet->name) {
+                    // End of row
+                    case 'row':
+                        if ($this->Worksheet->nodeType == XMLReader::END_ELEMENT) {
+                            $this->RowOpen = false;
+                            break 2;
+                        }
+                        break;
+                    // Cell
+                    case 'c':
+                        // If it is a closing tag, skip it
+                        if ($this->Worksheet->nodeType == XMLReader::END_ELEMENT) {
+                            continue;
+                        }
+                        $StyleId = (int)$this->Worksheet->getAttribute('s');
+                        // Get the index of the cell
+                        $Index = $this->Worksheet->getAttribute('r');
+                        $Letter = preg_replace('{[^[:alpha:]]}S', '', $Index);
+                        $Index = self::IndexFromColumnLetter($Letter);
+                        // Determine cell type
+                        if ($this->Worksheet->getAttribute('t') == self::CELL_TYPE_SHARED_STR) {
+                            $CellHasSharedString = true;
+                        } else {
+                            $CellHasSharedString = false;
+                        }
+                        $this->CurrentRow[$Index] = '';
+                        $CellCount++;
+                        if ($Index > $MaxIndex) {
+                            $MaxIndex = $Index;
+                        }
+                        break;
+                    // Cell value
+                    case 'v':
+                    case 'is':
+                        if ($this->Worksheet->nodeType == XMLReader::END_ELEMENT) {
+                            continue;
+                        }
+                        $Value = $this->Worksheet->readString();
+                        if ($CellHasSharedString) {
+                            $Value = $this->GetSharedString($Value);
+                        }
+                        // Format value if necessary
+                        if ($Value !== '' && $StyleId && isset($this->Styles[$StyleId])) {
+                            $Value = $this->FormatValue($Value, $StyleId);
+                        } elseif ($Value) {
+                            $Value = $this->GeneralFormat($Value);
+                        }
+                        $this->CurrentRow[$Index] = $Value;
+                        break;
+                }
+            }
+            // Adding empty cells, if necessary
+            // Only empty cells inbetween and on the left side are added
+            if ($MaxIndex + 1 > $CellCount) {
+                $this->CurrentRow = $this->CurrentRow + array_fill(0, $MaxIndex + 1, '');
+                ksort($this->CurrentRow);
+            }
+        }
+
+        return $this->CurrentRow;
+    }
+
+    /**
+     * Return the identifying key of the current element.
+     * Similar to the key() function for arrays in PHP
+     * @return mixed either an integer or a string
+     */
+    public function key() {
+        return $this->Index;
+    }
+
+    /**
+     * Check if there is a current element after calls to rewind() or next().
+     * Used to check if we've iterated to the end of the collection
+     * @return boolean FALSE if there's nothing more to iterate over
+     */
+    public function valid() {
+        return $this->Valid;
+    }
+
+    // !Countable interface method
+    /**
+     * Ostensibly should return the count of the contained items but this just returns the number
+     * of rows read so far. It's not really correct but at least coherent.
+     */
+    public function count() {
+        if (! isset($this->rowCount)) {
+            $total = 0;
+            $this->rewind();
+
+            while ($this->Worksheet->read()) {
+                if ($this->Worksheet->name == 'row' && $this->Worksheet->nodeType != XMLReader::END_ELEMENT) {
+                    $total++;
+                }
+            }
+            $this->rowCount = $total;
+        }
+
+        return $this->rowCount;
+    }
+
+    /**
+     * Takes the column letter and converts it to a numerical index (0-based)
+     *
+     * @param string Letter(s) to convert
+     *
+     * @return mixed Numeric index (0-based) or boolean false if it cannot be calculated
+     */
+    public static function IndexFromColumnLetter($Letter) {
+        $Powers = array();
+        $Letter = strtoupper($Letter);
+        $Result = 0;
+        for ($i = strlen($Letter) - 1, $j = 0; $i >= 0; $i--, $j++) {
+            $Ord = ord($Letter[$i]) - 64;
+            if ($Ord > 26) {
+                // Something is very, very wrong
+                return false;
+            }
+            $Result += $Ord*pow(26, $j);
+        }
+
+        return $Result - 1;
+    }
+
+    /**
+     * Helper function for greatest common divisor calculation in case GMP extension is not enabled
+     *
+     * @param int Number #1
+     * @param int Number #2
+     * @param int Greatest common divisor
+     * @return int
+     */
+    public static function GCD($A, $B) {
+        $A = abs($A);
+        $B = abs($B);
+        if ($A + $B == 0) {
+            return 0;
+        } else {
+            $C = 1;
+            while ($A > 0) {
+                $C = $A;
+                $A = $B%$A;
+                $B = $C;
+            }
+
+            return $C;
+        }
+    }
 }

+ 273 - 268
PHPExcelReader/PHPExcelReader.php

@@ -1,278 +1,283 @@
 <?php
 /**
  * PHPExcelReader class
- *
  * @version 1.0.0
- * @author Janson Leung
+ * @author  Janson Leung
  */
 
 /** PHPExcel root directory */
-if ( ! defined('PHPEXCEL_ROOT')) {
-	define('PHPEXCEL_ROOT', dirname(__FILE__) . '/');
-	require(PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
+if (! defined('PHPEXCEL_ROOT')) {
+    define('PHPEXCEL_ROOT', dirname(__FILE__) . '/');
+    require(PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
 }
 
 class PHPExcelReader implements SeekableIterator, Countable {
-	const TYPE_XLSX = 'XLSX';
-	const TYPE_XLS = 'XLS';
-	const TYPE_CSV = 'CSV';
-
-	private $handle;
-	private $type;
-	private $index = 0;
-
-	/**
-	 * @param string Path to file
-	 * @param string Original filename (in case of an uploaded file), used to determine file type, optional
-	 * @param string MIME type from an upload, used to determine file type, optional
-	 */
-	public function __construct($filePath, $originalFileName = false, $mimeType = false) {
-		if ( ! is_readable($filePath)) {
-			throw new Exception('PHPExcel_Reader: File (' . $filePath . ') not readable');
-		}
-
-		$defaultTimeZone = @date_default_timezone_get();
-		if ($defaultTimeZone)	{
-			date_default_timezone_set($defaultTimeZone);
-		}
-
-		// Checking the other parameters for correctness
-		// This should be a check for string but we're lenient
-		if ( ! empty($originalFileName) && ! is_scalar($originalFileName)) {
-			throw new Exception('PHPExcel_Reader: Original file (2nd parameter) is not a string or a scalar value.');
-		}
-		if ( ! empty($mimeType) && ! is_scalar($mimeType)) {
-			throw new Exception('PHPExcel_Reader: Mime type (3nd parameter) is not a string or a scalar value.');
-		}
-
-		// 1. Determine type
-		if ( ! $originalFileName) {
-			$originalFileName = $filePath;
-		}
-
-		$Extension = strtolower(pathinfo($originalFileName, PATHINFO_EXTENSION));
-		if($mimeType) {
-			switch ($mimeType) {
-				case 'application/octet-stream':
-					$this->type = $Extension == 'xlsx' ? self::TYPE_XLSX : self::TYPE_CSV;
-					break;
-				case 'text/x-comma-separated-values':
-				case 'text/comma-separated-values':
-				case 'application/x-csv':
-				case 'text/x-csv':
-				case 'text/csv':
-				case 'application/csv':
-				case 'application/vnd.msexcel':
-				case 'text/plain':
-					$this->type = self::TYPE_CSV;
-					break;
-				case 'application/msexcel':
-				case 'application/x-msexcel':
-				case 'application/x-ms-excel':
-				case 'application/x-excel':
-				case 'application/x-dos_ms_excel':
-				case 'application/xls':
-				case 'application/x-xls':
-				case 'application/download':
-				case 'application/vnd.ms-office':
-				case 'application/msword':
-				case 'application/xlt':
-					$this->type = self::TYPE_XLS;
-					break;
-				case 'application/vnd.ms-excel':
-				case 'application/excel':
-					$this->type = $Extension == 'csv' ? self::TYPE_CSV : self::TYPE_XLS;
-					break;
-				case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
-				case 'application/vnd.openxmlformats-officedocument.spreadsheetml.template':
-				case 'application/zip':
-				case 'application/msword':
-				case 'application/x-zip':
-				case 'application/xlsx':
-				case 'application/xltx':
-					$this->type = self::TYPE_XLSX;
-					break;
-			}
-		}
-
-		if ( ! $this->type) {
-			switch ($Extension) {
-				case 'xlsx':
-				case 'xltx': // XLSX template
-				case 'xlsm': // Macro-enabled XLSX
-				case 'xltm': // Macro-enabled XLSX template
-					$this->type = self::TYPE_XLSX;
-					break;
-				case 'xls':
-				case 'xlt':
-					$this->type = self::TYPE_XLS;
-					break;
-				default:
-					$this->type = self::TYPE_CSV;
-					break;
-			}
-		}
-
-		// Pre-checking XLS files, in case they are renamed CSV or XLSX files
-		if ($this->type == self::TYPE_XLS) {
-			$this->handle = new PHPExcel_Reader_XLS($filePath);
-			if ($this->handle->error) {
-				$this->handle->__destruct();
-
-				if (is_resource($Ziphandle = zip_open($filePath))) {
-					$this->type = self::TYPE_XLSX;
-					zip_close($Ziphandle);
-				}
-				else {
-					$this->type = self::TYPE_CSV;
-				}
-			}
-		}
-
-		// 2. Create handle
-		switch ($this->type) {
-			case self::TYPE_XLSX:
-				$this->handle = new PHPExcel_Reader_XLSX($filePath);
-				break;
-			case self::TYPE_CSV:
-				$this->handle = new PHPExcel_Reader_CSV($filePath, 1);
-				break;
-			case self::TYPE_XLS:
-				// Everything already happens above
-				break;
-		}
-	}
-
-	/**
-	 * get the type of file
-	 * @return string
-	 */
-	public function getFileType() {
-		return $this->type;
-	}
-
-	/**
-	 * Gets information about separate sheets in the given file
-	 *
-	 * @return array Associative array where key is sheet index and value is sheet name
-	 */
-	public function Sheets() {
-		return $this->handle->Sheets();
-	}
-
-	/**
-	 * Changes the current sheet to another from the file.
-	 *	Note that changing the sheet will rewind the file to the beginning, even if
-	 *	the current sheet index is provided.
-	 *
-	 * @param int Sheet index
-	 *
-	 * @return bool True if sheet could be changed to the specified one,
-	 *	false if not (for example, if incorrect index was provided.
-	 */
-	public function ChangeSheet($index) {
-		return $this->handle->ChangeSheet($index);
-	}
-
-	/**
-	 * Rewind the Iterator to the first element.
-	 * Similar to the reset() function for arrays in PHP
-	 */ 
-	public function rewind() {
-		$this->index = 0;
-		if ($this->handle) {
-			$this->handle->rewind();
-		}
-	}
-
-	/** 
-	 * Return the current element.
-	 * Similar to the current() function for arrays in PHP
-	 *
-	 * @return mixed current element from the collection
-	 */
-	public function current() {
-		if ($this->handle) {
-			return $this->handle->current();
-		}
-		return null;
-	}
-
-	/** 
-	 * Move forward to next element. 
-	 * Similar to the next() function for arrays in PHP 
-	 */ 
-	public function next() {
-		if ($this->handle) {
-			$this->index++;
-			return $this->handle->next();
-		}
-		return null;
-	}
-
-	/** 
-	 * Return the identifying key of the current element.
-	 * Similar to the key() function for arrays in PHP
-	 *
-	 * @return mixed either an integer or a string
-	 */ 
-	public function key() {
-		if ($this->handle) {
-			return $this->handle->key();
-		}
-		return null;
-	}
-
-	/** 
-	 * Check if there is a current element after calls to rewind() or next().
-	 * Used to check if we've iterated to the end of the collection
-	 *
-	 * @return boolean FALSE if there's nothing more to iterate over
-	 */ 
-	public function valid()	{
-		if ($this->handle) {
-			return $this->handle->valid();
-		}
-		return false;
-	}
-
-	/**
-	 * total of file number
-	 * return int
-	 */
-	public function count()	{
-		if ($this->handle) {
-			return $this->handle->count();
-		}
-		return 0;
-	}
-
-	/**
-	 * Method for SeekableIterator interface. Takes a posiiton and traverses the file to that position
-	 * The value can be retrieved with a `current()` call afterwards.
-	 *
-	 * @param int position in file
-	 */
-	public function seek($position)	{
-		if ( ! $this->handle) {
-			throw new OutOfBoundsException('PHPExcel_Reader: No file opened');
-		}
-
-		$Currentindex = $this->handle->key();
-		if ($Currentindex != $position) {
-			if ($position < $Currentindex || is_null($Currentindex) || $position == 0) {
-				$this->rewind();
-			}
-
-			while ($this->handle->valid() && ($position > $this->handle->key())) {
-				$this->handle->next();
-			}
-
-			if ( ! $this->handle->valid()) {
-				throw new OutOfBoundsException('PHPExcel_Reader: position ' . $position . ' not found');
-			}
-		}
-
-		return null;
-	}
-}
+    const TYPE_XLSX = 'XLSX';
+    const TYPE_XLS = 'XLS';
+    const TYPE_CSV = 'CSV';
+
+    private $handle;
+
+    private $type;
+
+    private $index = 0;
+
+    /**
+     * @param string $filePath         Path to file
+     * @param string $originalFileName Original filename (in case of an uploaded file), used to determine file type,
+     *                                 optional
+     * @param string $mimeType         MIME type from an upload, used to determine file type, optional
+     * @throws Exception
+     */
+    public function __construct($filePath, $originalFileName = '', $mimeType = '') {
+        if (! is_readable($filePath)) {
+            throw new Exception('PHPExcel_Reader: File (' . $filePath . ') not readable');
+        }
+
+        $defaultTimeZone = @date_default_timezone_get();
+        if ($defaultTimeZone) {
+            date_default_timezone_set($defaultTimeZone);
+        }
+
+        // Checking the other parameters for correctness
+        // This should be a check for string but we're lenient
+        if (! empty($originalFileName) && ! is_scalar($originalFileName)) {
+            throw new Exception('PHPExcel_Reader: Original file (2nd parameter) is not a string or a scalar value.');
+        }
+        if (! empty($mimeType) && ! is_scalar($mimeType)) {
+            throw new Exception('PHPExcel_Reader: Mime type (3nd parameter) is not a string or a scalar value.');
+        }
+
+        // 1. Determine type
+        if (! $originalFileName) {
+            $originalFileName = $filePath;
+        }
+
+        $mimeType = $mimeType ?: mime_content_type($filePath);
+        $Extension = strtolower(pathinfo($originalFileName, PATHINFO_EXTENSION));
+        if ($mimeType) {
+            switch ($mimeType) {
+                case 'application/octet-stream':
+                    $this->type = $Extension == 'xlsx' ? self::TYPE_XLSX : self::TYPE_CSV;
+                    break;
+                case 'text/x-comma-separated-values':
+                case 'text/comma-separated-values':
+                case 'application/x-csv':
+                case 'text/x-csv':
+                case 'text/csv':
+                case 'application/csv':
+                case 'application/vnd.msexcel':
+                case 'text/plain':
+                    $this->type = self::TYPE_CSV;
+                    break;
+                case 'application/msexcel':
+                case 'application/x-msexcel':
+                case 'application/x-ms-excel':
+                case 'application/x-excel':
+                case 'application/x-dos_ms_excel':
+                case 'application/xls':
+                case 'application/x-xls':
+                case 'application/download':
+                case 'application/vnd.ms-office':
+                case 'application/msword':
+                case 'application/xlt':
+                    $this->type = self::TYPE_XLS;
+                    break;
+                case 'application/vnd.ms-excel':
+                case 'application/excel':
+                    $this->type = $Extension == 'csv' ? self::TYPE_CSV : self::TYPE_XLS;
+                    break;
+                case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
+                case 'application/vnd.openxmlformats-officedocument.spreadsheetml.template':
+                case 'application/zip':
+                case 'application/x-zip':
+                case 'application/xlsx':
+                case 'application/xltx':
+                    $this->type = self::TYPE_XLSX;
+                    break;
+            }
+        }
+
+        if (! $this->type) {
+            switch ($Extension) {
+                case 'xlsx':
+                case 'xltx': // XLSX template
+                case 'xlsm': // Macro-enabled XLSX
+                case 'xltm': // Macro-enabled XLSX template
+                    $this->type = self::TYPE_XLSX;
+                    break;
+                case 'xls':
+                case 'xlt':
+                    $this->type = self::TYPE_XLS;
+                    break;
+                default:
+                    $this->type = self::TYPE_CSV;
+                    break;
+            }
+        }
+
+        // Pre-checking XLS files, in case they are renamed CSV or XLSX files
+        if ($this->type == self::TYPE_XLS) {
+            $this->handle = new PHPExcel_Reader_XLS($filePath);
+            if ($this->handle->error) {
+                $this->handle->__destruct();
+
+                if (is_resource($Ziphandle = zip_open($filePath))) {
+                    $this->type = self::TYPE_XLSX;
+                    zip_close($Ziphandle);
+                } else {
+                    $this->type = self::TYPE_CSV;
+                }
+            }
+        }
+
+        // 2. Create handle
+        switch ($this->type) {
+            case self::TYPE_XLSX:
+                $this->handle = new PHPExcel_Reader_XLSX($filePath);
+                break;
+            case self::TYPE_CSV:
+                $this->handle = new PHPExcel_Reader_CSV($filePath, 1);
+                break;
+            case self::TYPE_XLS:
+                // Everything already happens above
+                break;
+        }
+    }
+
+    /**
+     * get the type of file
+     * @return string
+     */
+    public function getFileType() {
+        return $this->type;
+    }
+
+    /**
+     * Gets information about separate sheets in the given file
+     * @return array Associative array where key is sheet index and value is sheet name
+     */
+    public function Sheets() {
+        return $this->handle->Sheets();
+    }
+
+    /**
+     * Changes the current sheet to another from the file.
+     *    Note that changing the sheet will rewind the file to the beginning, even if
+     *    the current sheet index is provided.
+     *
+     * @param int $index Sheet index
+     *
+     * @return bool True if sheet could be changed to the specified one,
+     *    false if not (for example, if incorrect index was provided.
+     */
+    public function ChangeSheet($index) {
+        return $this->handle->ChangeSheet($index);
+    }
+
+    /**
+     * Rewind the Iterator to the first element.
+     * Similar to the reset() function for arrays in PHP
+     */
+    public function rewind() {
+        $this->index = 0;
+        if ($this->handle) {
+            $this->handle->rewind();
+        }
+    }
+
+    /**
+     * Return the current element.
+     * Similar to the current() function for arrays in PHP
+     * @return mixed current element from the collection
+     */
+    public function current() {
+        if ($this->handle) {
+            return $this->handle->current();
+        }
+
+        return null;
+    }
+
+    /**
+     * Move forward to next element.
+     * Similar to the next() function for arrays in PHP
+     */
+    public function next() {
+        if ($this->handle) {
+            $this->index++;
+
+            return $this->handle->next();
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the identifying key of the current element.
+     * Similar to the key() function for arrays in PHP
+     * @return mixed either an integer or a string
+     */
+    public function key() {
+        if ($this->handle) {
+            return $this->handle->key();
+        }
+
+        return null;
+    }
+
+    /**
+     * Check if there is a current element after calls to rewind() or next().
+     * Used to check if we've iterated to the end of the collection
+     * @return boolean FALSE if there's nothing more to iterate over
+     */
+    public function valid() {
+        if ($this->handle) {
+            return $this->handle->valid();
+        }
+
+        return false;
+    }
+
+    /**
+     * total of file number
+     * return int
+     */
+    public function count() {
+        if ($this->handle) {
+            return $this->handle->count();
+        }
+
+        return 0;
+    }
+
+    /**
+     * Method for SeekableIterator interface. Takes a posiiton and traverses the file to that position
+     * The value can be retrieved with a `current()` call afterwards.
+     *
+     * @param int $position position in file
+     * @return null
+     */
+    public function seek($position) {
+        if (! $this->handle) {
+            throw new OutOfBoundsException('PHPExcel_Reader: No file opened');
+        }
+
+        $Currentindex = $this->handle->key();
+        if ($Currentindex != $position) {
+            if ($position < $Currentindex || is_null($Currentindex) || $position == 0) {
+                $this->rewind();
+            }
+
+            while ($this->handle->valid() && ($position > $this->handle->key())) {
+                $this->handle->next();
+            }
+
+            if (! $this->handle->valid()) {
+                throw new OutOfBoundsException('PHPExcel_Reader: position ' . $position . ' not found');
+            }
+        }
+
+        return null;
+    }
+}