'; if ( $xls = SimpleXLS::parse('excel5book.xls')) { print_r( $xls->rows() ); // dump first sheet print_r( $xls->rows(1)); /// dump second sheet } else { echo SimpleXLSX::parseError(); } echo ''; */ /** * A class for reading Microsoft Excel Spreadsheets. * * SimpleXLS version 2016-2020 packaged by Sergey Shuchkin from * Spreadsheet_Excel_Reader class developed by Vadim Tkachenko */ class SimpleXLS { const BIFF8 = 0x600; const BIFF7 = 0x500; const WORKBOOKGLOBALS = 0x5; const WORKSHEET = 0x10; //const TYPE_BOF = 0x809; const TYPE_EOF = 0x0a; const TYPE_BOUNDSHEET = 0x85; const TYPE_DIMENSION = 0x200; const TYPE_ROW = 0x208; const TYPE_DBCELL = 0xd7; const TYPE_FILEPASS = 0x2f; //const TYPE_NOTE = 0x1c; //const TYPE_TXO = 0x1b6; const TYPE_RK = 0x7e; const TYPE_RK2 = 0x27e; const TYPE_MULRK = 0xbd; const TYPE_MULBLANK = 0xbe; //const TYPE_INDEX = 0x20b; const TYPE_SST = 0xfc; //const TYPE_EXTSST = 0xff; //const TYPE_CONTINUE = 0x3c; const TYPE_LABEL = 0x204; const TYPE_LABELSST = 0xfd; const TYPE_NUMBER = 0x203; const TYPE_NAME = 0x18; //const TYPE_ARRAY = 0x221; //const TYPE_STRING = 0x207; const TYPE_FORMULA = 0x406; const TYPE_FORMULA2 = 0x6; const TYPE_FORMAT = 0x41e; const TYPE_XF = 0xe0; const TYPE_BOOLERR = 0x205; //const TYPE_UNKNOWN = 0xffff; const TYPE_NINETEENFOUR = 0x22; const TYPE_MERGEDCELLS = 0xE5; //const DEF_NUM_FORMAT = "%.2f"; const DEF_NUM_FORMAT = '%s'; // OLE const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2c; const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3c; const ROOT_START_BLOCK_POS = 0x30; const BIG_BLOCK_SIZE = 0x200; const SMALL_BLOCK_SIZE = 0x40; const EXTENSION_BLOCK_POS = 0x44; const NUM_EXTENSION_BLOCK_POS = 0x48; const PROPERTY_STORAGE_BLOCK_SIZE = 0x80; const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4c; const SMALL_BLOCK_THRESHOLD = 0x1000; // property storage offsets const SIZE_OF_NAME_POS = 0x40; const TYPE_POS = 0x42; const START_BLOCK_POS = 0x74; const SIZE_POS = 0x78; /** * Array of worksheets found * * @var array * @access public */ public $boundsheets = array(); /** * Array of format records found * * @var array * @access public */ public $formatRecords = array(); /** * * @var array * @access public */ public $sst = array(); /** * Array of worksheets * * The data is stored in 'cells' and the meta-data is stored in an array * called 'cellsInfo' * * Example: * * $sheets --> 'cells' --> row --> column --> Interpreted value * --> 'cellsInfo' --> row --> column --> 'type' - Can be 'date', 'number', or 'unknown' * --> 'raw' - The raw data that Excel stores for that data cell * * @var array * @access public */ public $sheets = array(); /** * List of default date formats used by Excel * * @var array * @access public */ public $dateFormats = array( 0xe => 'd/m/Y', 0xf => 'd-M-Y', 0x10 => 'd-M', 0x11 => 'M-Y', 0x12 => 'h:i a', 0x13 => 'h:i:s a', 0x14 => 'H:i', 0x15 => 'H:i:s', 0x16 => 'd/m/Y H:i', 0x2d => 'i:s', 0x2e => 'H:i:s', 0x2f => 'i:s.S' ); /** * Default number formats used by Excel * * @var array * @access public */ public $numberFormats = array( 0x1 => '%1.0f', // "0" 0x2 => '%1.2f', // "0.00", 0x3 => '%1.0f', //"#,##0", 0x4 => '%1.2f', //"#,##0.00", 0x5 => '%1.0f', /*"$#,##0;($#,##0)",*/ 0x6 => '$%1.0f', /*"$#,##0;($#,##0)",*/ 0x7 => '$%1.2f', //"$#,##0.00;($#,##0.00)", 0x8 => '$%1.2f', //"$#,##0.00;($#,##0.00)", 0x9 => '%1.0f%%', // "0%" 0xa => '%1.2f%%', // "0.00%" 0xb => '%1.2f', // 0.00E00", 0x25 => '%1.0f', // "#,##0;(#,##0)", 0x26 => '%1.0f', //"#,##0;(#,##0)", 0x27 => '%1.2f', //"#,##0.00;(#,##0.00)", 0x28 => '%1.2f', //"#,##0.00;(#,##0.00)", 0x29 => '%1.0f', //"#,##0;(#,##0)", 0x2a => '$%1.0f', //"$#,##0;($#,##0)", 0x2b => '%1.2f', //"#,##0.00;(#,##0.00)", 0x2c => '$%1.2f', //"$#,##0.00;($#,##0.00)", 0x30 => '%1.0f' ); private $datetimeFormat = 'Y-m-d H:i:s'; /** * Default encoding * * @var string * @access private */ private $defaultEncoding = 'UTF-8'; /** * Default number format * * @var integer * @access private */ private $defaultFormat = self::DEF_NUM_FORMAT; /** * List of formats to use for each column * * @var array * @access private */ private $columnsFormat = array(); /** * * @var integer * @access private */ private $rowoffset = 1; //"##0.0E0"; // }}} /** * * @var integer * @access private */ private $colOffset = 1; private $recType; private $nineteenFour; private $multiplier; private $sn; private $curFormat; // OLERead private $data; private $numBigBlockDepotBlocks; private $sbdStartBlock; private $rootStartBlock; private $extensionBlock; private $numExtensionBlocks; private $bigBlockChain; private $smallBlockChain; private $rootEntry; private $entry; private $props; // sergey.shuchkin@gmail.com private $wrkbook; // false - to use excel format private $error = false; private $debug; // {{{ Spreadsheet_Excel_Reader() /** * Constructor * * @param string $filename XLS Filename or xls contents * @param bool $isData If True then $filename is contents * @param bool $debug Trigger PHP errors? */ public function __construct( $filename, $isData = false, $debug = false ) { $this->debug = $debug; $this->_oleread( $filename, $isData ); $this->_parse(); } public static function parse( $filename, $isData = false, $debug = false ) { $xlsx = new self( $filename, $isData, $debug ); if ( $xlsx->success() ) { return $xlsx; } self::parseError( $xlsx->error() ); return false; } public static function parseError( $set = false ) { static $error = false; return $set ? $error = $set : $error; } public function error( $set = false ) { if ( $set ) { $this->error = $set; if ( $this->debug ) { trigger_error( $set ); } } return $this->error; } public function success() { return ! $this->error; } public function rows( $sheetNum = 0 ) { if ( $this->sheets[ $sheetNum ] ) { $s = $this->sheets[ $sheetNum ]; $result = array(); for ( $i = 0; $i < $s['numRows']; $i ++ ) { $r = array(); for ( $j = 0; $j < $s['numCols']; $j ++ ) { $r[ $j ] = isset( $s['cells'][ $i + 1 ][ $j + 1 ] ) ? $s['cells'][ $i + 1 ][ $j + 1 ] : ''; } $result[] = $r; } return $result; } return false; } public function setDateTimeFormat( $value ) { $this->datetimeFormat = is_string( $value) ? $value : false; } // }}} private function _oleread( $sFileName, $isData = false ) { if ( $isData ) { $this->data = $sFileName; } else { // check if file exist and is readable (Darko Miljanovic) if ( ! is_readable( $sFileName ) ) { $this->error( 'File not is readable ' . $sFileName ); return false; } $this->data = file_get_contents( $sFileName ); if ( ! $this->data ) { $this->error( 'File reading error ' . $sFileName ); return false; } } //echo IDENTIFIER_OLE; //echo 'start'; if ( $this->_strpos( $this->data, pack( 'CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 ) ) !== 0 ) { $this->error( 'File is not XLS' ); return false; } $this->numBigBlockDepotBlocks = $this->_GetInt4d( $this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS ); $this->sbdStartBlock = $this->_GetInt4d( $this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS ); $this->rootStartBlock = $this->_GetInt4d( $this->data, self::ROOT_START_BLOCK_POS ); $this->extensionBlock = $this->_GetInt4d( $this->data, self::EXTENSION_BLOCK_POS ); $this->numExtensionBlocks = $this->_GetInt4d( $this->data, self::NUM_EXTENSION_BLOCK_POS ); /* echo $this->numBigBlockDepotBlocks." "; echo $this->sbdStartBlock." "; echo $this->rootStartBlock." "; echo $this->extensionBlock." "; echo $this->numExtensionBlocks." "; */ //echo "sbdStartBlock = $this->sbdStartBlock\n"; $bigBlockDepotBlocks = array(); $pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS; // echo "pos = $pos"; $bbdBlocks = $this->numBigBlockDepotBlocks; if ( $this->numExtensionBlocks !== 0 ) { $bbdBlocks = ( self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS ) / 4; } for ( $i = 0; $i < $bbdBlocks; $i ++ ) { $bigBlockDepotBlocks[ $i ] = $this->_GetInt4d( $this->data, $pos ); $pos += 4; } for ( $j = 0; $j < $this->numExtensionBlocks; $j ++ ) { $pos = ( $this->extensionBlock + 1 ) * self::BIG_BLOCK_SIZE; $blocksToRead = min( $this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1 ); for ( $i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; $i ++ ) { $bigBlockDepotBlocks[ $i ] = $this->_GetInt4d( $this->data, $pos ); $pos += 4; } $bbdBlocks += $blocksToRead; if ( $bbdBlocks < $this->numBigBlockDepotBlocks ) { $this->extensionBlock = $this->_GetInt4d( $this->data, $pos ); } } // var_dump($bigBlockDepotBlocks); // readBigBlockDepot $index = 0; $this->bigBlockChain = array(); for ( $i = 0; $i < $this->numBigBlockDepotBlocks; $i ++ ) { $pos = ( $bigBlockDepotBlocks[ $i ] + 1 ) * self::BIG_BLOCK_SIZE; //echo "pos = $pos"; for ( $j = 0; $j < self::BIG_BLOCK_SIZE / 4; $j ++ ) { $this->bigBlockChain[ $index ] = $this->_GetInt4d( $this->data, $pos ); $pos += 4; $index ++; } } //var_dump($this->bigBlockChain); //echo '=====2'; // readSmallBlockDepot(); $index = 0; $sbdBlock = $this->sbdStartBlock; $this->smallBlockChain = array(); while ( $sbdBlock !== - 2 ) { $pos = ( $sbdBlock + 1 ) * self::BIG_BLOCK_SIZE; for ( $j = 0; $j < self::BIG_BLOCK_SIZE / 4; $j ++ ) { $this->smallBlockChain[ $index ] = $this->_GetInt4d( $this->data, $pos ); $pos += 4; $index ++; } $sbdBlock = $this->bigBlockChain[ $sbdBlock ]; } // readData(rootStartBlock) $block = $this->rootStartBlock; $this->entry = $this->_readData( $block ); /* while ($block != -2) { $pos = ($block + 1) * self::BIG_BLOCK_SIZE; $this->entry = $this->entry.$this->_substr($this->_data, $pos, self::BIG_BLOCK_SIZE); $block = $this->bigBlockChain[$block]; } */ //echo '==='.$this->entry."==="; $this->_readPropertySets(); $this->data = $this->_readWorkBook(); return true; } // {{{ setOutputEncoding() public function _GetInt4d( $data, $pos ) { $value = ord( $data[ $pos ] ) | ( ord( $data[ $pos + 1 ] ) << 8 ) | ( ord( $data[ $pos + 2 ] ) << 16 ) | ( ord( $data[ $pos + 3 ] ) << 24 ); return ($value > 0x7FFFFFFF) ? $value - 0x100000000 : $value; } // }}} // {{{ setRowColOffset() public function _readData( $bl ) { $block = $bl; $data = ''; while ( $block !== - 2 ) { $pos = ( $block + 1 ) * self::BIG_BLOCK_SIZE; $data .= $this->_substr( $this->data, $pos, self::BIG_BLOCK_SIZE ); //echo "pos = $pos data=$data\n"; $block = $this->bigBlockChain[ $block ]; } return $data; } // }}} // {{{ setDefaultFormat() public function _readPropertySets() { $offset = 0; //var_dump($this->entry); while ( $offset < $this->_strlen( $this->entry ) ) { $d = $this->_substr( $this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE ); $nameSize = ord( $d[ self::SIZE_OF_NAME_POS ] ) | ( ord( $d[ self::SIZE_OF_NAME_POS + 1 ] ) << 8 ); $type = ord( $d[ self::TYPE_POS ] ); //$maxBlock = $this->_strlen($d) / self::BIG_BLOCK_SIZE - 1; $startBlock = $this->_GetInt4d( $d, self::START_BLOCK_POS ); $size = $this->_GetInt4d( $d, self::SIZE_POS ); $name = ''; for ( $i = 0; $i < $nameSize; $i ++ ) { $name .= $d[ $i ]; } $name = str_replace( "\x00", '', $name ); $this->props[] = array( 'name' => $name, 'type' => $type, 'startBlock' => $startBlock, 'size' => $size ); if ( ( $name === 'Workbook' ) || ( $name === 'Book' ) ) { $this->wrkbook = count( $this->props ) - 1; } if ( $name === 'Root Entry' ) { $this->rootEntry = count( $this->props ) - 1; } //echo "name ==$name=\n"; $offset += self::PROPERTY_STORAGE_BLOCK_SIZE; } } // }}} // {{{ setColumnFormat() private function _readWorkBook() { if ( $this->props[ $this->wrkbook ]['size'] < self::SMALL_BLOCK_THRESHOLD ) { // getSmallBlockStream(PropertyStorage ps) $rootdata = $this->_readData( $this->props[ $this->rootEntry ]['startBlock'] ); $streamData = ''; $block = (int) $this->props[ $this->wrkbook ]['startBlock']; //$count = 0; while ( $block !== - 2 ) { $pos = $block * self::SMALL_BLOCK_SIZE; $streamData .= $this->_substr( $rootdata, $pos, self::SMALL_BLOCK_SIZE ); $block = $this->smallBlockChain[ $block ]; } return $streamData; } $numBlocks = $this->props[ $this->wrkbook ]['size'] / self::BIG_BLOCK_SIZE; if ( $this->props[ $this->wrkbook ]['size'] % self::BIG_BLOCK_SIZE !== 0 ) { $numBlocks ++; } if ( $numBlocks === 0 ) { return ''; } //echo "numBlocks = $numBlocks\n"; //byte[] streamData = new byte[numBlocks * self::BIG_BLOCK_SIZE]; //print_r($this->wrkbook); $streamData = ''; $block = $this->props[ $this->wrkbook ]['startBlock']; //echo "block = $block"; while ( $block !== - 2 ) { $pos = ( $block + 1 ) * self::BIG_BLOCK_SIZE; $streamData .= $this->_substr( $this->data, $pos, self::BIG_BLOCK_SIZE ); $block = $this->bigBlockChain[ $block ]; } //echo 'stream'.$streamData; return $streamData; } // }}} // {{{ _parse() /** * Parse a workbook * * @access private * @return bool */ public function _parse() { $pos = 0; // $code = ord($this->data[$pos]) | ord($this->data[$pos+1])<<8; $length = ord( $this->data[ $pos + 2 ] ) | ord( $this->data[ $pos + 3 ] ) << 8; $version = ord( $this->data[ $pos + 4 ] ) | ord( $this->data[ $pos + 5 ] ) << 8; $substreamType = ord( $this->data[ $pos + 6 ] ) | ord( $this->data[ $pos + 7 ] ) << 8; // echo "Start parse code=".base_convert($code,10,16)." version=".base_convert($version,10,16)." substreamType=".base_convert($substreamType,10,16).""."\n"; // die(); if ( ( $version !== self::BIFF8 ) && ( $version !== self::BIFF7 ) ) { return false; } if ( $substreamType !== self::WORKBOOKGLOBALS ) { return false; } //print_r($rec); $pos += $length + 4; $code = ord( $this->data[ $pos ] ) | ord( $this->data[ $pos + 1 ] ) << 8; $length = ord( $this->data[ $pos + 2 ] ) | ord( $this->data[ $pos + 3 ] ) << 8; while ( $code !== self::TYPE_EOF ) { switch ( $code ) { case self::TYPE_SST: //echo "Type_SST\n"; $formattingRuns = 0; $extendedRunLength = 0; $spos = $pos + 4; $limitpos = $spos + $length; $uniqueStrings = $this->_GetInt4d( $this->data, $spos + 4 ); $spos += 8; for ( $i = 0; $i < $uniqueStrings; $i ++ ) { // Read in the number of characters if ( $spos === $limitpos ) { $opcode = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8; $conlength = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; if ( $opcode !== 0x3c ) { return - 1; } $spos += 4; $limitpos = $spos + $conlength; } $numChars = ord( $this->data[ $spos ] ) | ( ord( $this->data[ $spos + 1 ] ) << 8 ); //echo "i = $i pos = $pos numChars = $numChars "; $spos += 2; $optionFlags = ord( $this->data[ $spos ] ); $spos ++; $asciiEncoding = ( ( $optionFlags & 0x01 ) === 0 ); $extendedString = ( ( $optionFlags & 0x04 ) !== 0 ); // See if string contains formatting information $richString = ( ( $optionFlags & 0x08 ) !== 0 ); if ( $richString ) { // Read in the crun $formattingRuns = ord( $this->data[ $spos ] ) | ( ord( $this->data[ $spos + 1 ] ) << 8 ); $spos += 2; } if ( $extendedString ) { // Read in cchExtRst $extendedRunLength = $this->_GetInt4d( $this->data, $spos ); $spos += 4; } $len = $asciiEncoding ? $numChars : $numChars * 2; if ( $spos + $len < $limitpos ) { $retstr = $this->_substr( $this->data, $spos, $len ); $spos += $len; } else { // found countinue $retstr = $this->_substr( $this->data, $spos, $limitpos - $spos ); $bytesRead = $limitpos - $spos; $charsLeft = $numChars - ( $asciiEncoding ? $bytesRead : ( $bytesRead / 2 ) ); $spos = $limitpos; while ( $charsLeft > 0 ) { $opcode = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8; $conlength = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; if ( $opcode !== 0x3c ) { return - 1; } $spos += 4; $limitpos = $spos + $conlength; $option = ord( $this->data[ $spos ] ); $spos ++; if ( $asciiEncoding && ( $option === 0 ) ) { $len = min( $charsLeft, $limitpos - $spos ); // min($charsLeft, $conlength); $retstr .= $this->_substr( $this->data, $spos, $len ); $charsLeft -= $len; $asciiEncoding = true; } elseif ( ! $asciiEncoding && ( $option !== 0 ) ) { $len = min( $charsLeft * 2, $limitpos - $spos ); // min($charsLeft, $conlength); $retstr .= $this->_substr( $this->data, $spos, $len ); $charsLeft -= $len / 2; $asciiEncoding = false; } elseif ( ! $asciiEncoding && ( $option === 0 ) ) { // Bummer - the string starts off as Unicode, but after the // continuation it is in straightforward ASCII encoding $len = min( $charsLeft, $limitpos - $spos ); // min($charsLeft, $conlength); for ( $j = 0; $j < $len; $j ++ ) { $retstr .= $this->data[ $spos + $j ] . chr( 0 ); } $charsLeft -= $len; $asciiEncoding = false; } else { $newstr = ''; for ( $j = 0, $len_retstr = $this->_strlen( $retstr ); $j < $len_retstr; $j ++ ) { $newstr = $retstr[ $j ] . chr( 0 ); } $retstr = $newstr; $len = min( $charsLeft * 2, $limitpos - $spos ); // min($charsLeft, $conlength); $retstr .= $this->_substr( $this->data, $spos, $len ); $charsLeft -= $len / 2; $asciiEncoding = false; //echo "Izavrat\n"; } $spos += $len; } } $retstr = $asciiEncoding ? $retstr : $this->_encodeUTF16( $retstr ); // echo "Str $i = $retstr\n"; if ( $richString ) { $spos += 4 * $formattingRuns; } // For extended strings, skip over the extended string data if ( $extendedString ) { $spos += $extendedRunLength; } //if ($retstr == 'Derby'){ // echo "bb\n"; //} $this->sst[] = $retstr; } /*$continueRecords = array(); while ($this->getNextCode() == Type_CONTINUE) { $continueRecords[] = &$this->nextRecord(); } //echo " 1 Type_SST\n"; $this->shareStrings = new SSTRecord($r, $continueRecords); //print_r($this->shareStrings->strings); */ // echo 'SST read: '.($time_end-$time_start)."\n"; break; case self::TYPE_FILEPASS: return false; break; case self::TYPE_NAME: //echo "Type_NAME\n"; break; case self::TYPE_FORMAT: $indexCode = ord( $this->data[ $pos + 4 ] ) | ord( $this->data[ $pos + 5 ] ) << 8; if ( $version === self::BIFF8 ) { $numchars = ord( $this->data[ $pos + 6 ] ) | ord( $this->data[ $pos + 7 ] ) << 8; if ( ord( $this->data[ $pos + 8 ] ) === 0 ) { $formatString = $this->_substr( $this->data, $pos + 9, $numchars ); } else { $formatString = $this->_substr( $this->data, $pos + 9, $numchars * 2 ); } } else { $numchars = ord( $this->data[ $pos + 6 ] ); $formatString = $this->_substr( $this->data, $pos + 7, $numchars * 2 ); } $this->formatRecords[ $indexCode ] = $formatString; // echo "Type.FORMAT\n"; break; case self::TYPE_XF: $formatstr = ''; //global $dateFormats, $numberFormats; $indexCode = ord( $this->data[ $pos + 6 ] ) | ord( $this->data[ $pos + 7 ] ) << 8; //echo "\nType.XF ".count($this->formatRecords['xfrecords'])." $indexCode "; if ( array_key_exists( $indexCode, $this->dateFormats ) ) { //echo "isdate ".$dateFormats[$indexCode]; $this->formatRecords['xfrecords'][] = array( 'type' => 'date', 'format' => $this->dateFormats[ $indexCode ] ); } elseif ( array_key_exists( $indexCode, $this->numberFormats ) ) { //echo "isnumber ".$this->numberFormats[$indexCode]; $this->formatRecords['xfrecords'][] = array( 'type' => 'number', 'format' => $this->numberFormats[ $indexCode ] ); } else { $isdate = false; if ( $indexCode > 0 ) { if ( isset( $this->formatRecords[ $indexCode ] ) ) { $formatstr = $this->formatRecords[ $indexCode ]; } //echo '.other.'; // echo "\ndate-time=$formatstr=\n"; if ( $formatstr && preg_match( "/^[hmsday\/\-:\s]+$/i", $formatstr ) === 1 ) { // found day and time format $isdate = true; $formatstr = str_replace( array( 'mm', 'h' ), array( 'i', 'H' ), $formatstr ); //echo "\ndate-time $formatstr \n"; } } if ( $isdate ) { $this->formatRecords['xfrecords'][] = array( 'type' => 'date', 'format' => $formatstr, ); } else { $this->formatRecords['xfrecords'][] = array( 'type' => 'other', 'format' => '', 'code' => $indexCode ); } } //echo "\n"; break; case self::TYPE_NINETEENFOUR: //echo "Type.NINETEENFOUR\n"; $this->nineteenFour = ( ord( $this->data[ $pos + 4 ] ) === 1 ); break; case self::TYPE_BOUNDSHEET: //echo "Type.BOUNDSHEET\n"; $rec_offset = $this->_GetInt4d( $this->data, $pos + 4 ); // $rec_typeFlag = ord($this->_data[$pos + 8]); // $rec_visibilityFlag = ord($this->_data[$pos + 9]); $rec_length = ord( $this->data[ $pos + 10 ] ); $rec_name = ''; if ( $version === self::BIFF8 ) { $chartype = ord( $this->data[ $pos + 11 ] ); if ( $chartype === 0 ) { $rec_name = $this->_substr( $this->data, $pos + 12, $rec_length ); } else { $rec_name = $this->_encodeUTF16( $this->_substr( $this->data, $pos + 12, $rec_length * 2 ) ); } } elseif ( $version === self::BIFF7 ) { $rec_name = $this->_substr( $this->data, $pos + 11, $rec_length ); } $this->boundsheets[] = array( 'name' => $rec_name, 'offset' => $rec_offset ); break; } //echo "Code = ".base_convert($r['code'],10,16)."\n"; $pos += $length + 4; $code = ord( $this->data[ $pos ] ) | ord( $this->data[ $pos + 1 ] ) << 8; $length = ord( $this->data[ $pos + 2 ] ) | ord( $this->data[ $pos + 3 ] ) << 8; //$r = &$this->nextRecord(); //echo "1 Code = ".base_convert($r['code'],10,16)."\n"; } foreach ( $this->boundsheets as $key => $val ) { $this->sn = $key; $this->_parsesheet( $val['offset'] ); } return true; } public function _encodeUTF16( $string ) { $result = $string; if ( $this->defaultEncoding ) { $result = mb_convert_encoding( $string, $this->defaultEncoding, 'UTF-16LE' ); } return $result; } public function _parsesheet( $spos ) { $cont = true; // read BOF // $code = ord($this->_data[$spos]) | ord($this->_data[$spos + 1]) << 8; $length = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; $version = ord( $this->data[ $spos + 4 ] ) | ord( $this->data[ $spos + 5 ] ) << 8; $substreamType = ord( $this->data[ $spos + 6 ] ) | ord( $this->data[ $spos + 7 ] ) << 8; if ( ( $version !== self::BIFF8 ) && ( $version !== self::BIFF7 ) ) { return - 1; } if ( $substreamType !== self::WORKSHEET ) { return - 2; } //echo "Start parse code=".base_convert($code,10,16)." version=".base_convert($version,10,16)." substreamType=".base_convert($substreamType,10,16).""."\n"; $spos += $length + 4; //var_dump($this->formatRecords); //echo "code $code $length"; while ( $cont ) { //echo "mem= ".memory_get_usage()."\n"; // $r = &$this->file->nextRecord(); $lowcode = ord( $this->data[ $spos ] ); if ( $lowcode === self::TYPE_EOF ) { break; } $code = $lowcode | ord( $this->data[ $spos + 1 ] ) << 8; $length = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; $spos += 4; $this->sheets[ $this->sn ]['maxrow'] = $this->rowoffset - 1; $this->sheets[ $this->sn ]['maxcol'] = $this->colOffset - 1; //echo "Code=".base_convert($code,10,16)." $code\n"; unset( $this->recType ); $this->multiplier = 1; // need for format with % switch ( $code ) { case self::TYPE_DIMENSION: //echo 'Type_DIMENSION '; if ( ! isset( $this->numRows ) ) { if ( ( $length === 10 ) || ( $version === self::BIFF7 ) ) { $this->sheets[ $this->sn ]['numRows'] = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; $this->sheets[ $this->sn ]['numCols'] = ord( $this->data[ $spos + 6 ] ) | ord( $this->data[ $spos + 7 ] ) << 8; } else { $this->sheets[ $this->sn ]['numRows'] = ord( $this->data[ $spos + 4 ] ) | ord( $this->data[ $spos + 5 ] ) << 8; $this->sheets[ $this->sn ]['numCols'] = ord( $this->data[ $spos + 10 ] ) | ord( $this->data[ $spos + 11 ] ) << 8; } } //echo 'numRows '.$this->numRows.' '.$this->numCols."\n"; break; case self::TYPE_MERGEDCELLS: $cellRanges = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8; for ( $i = 0; $i < $cellRanges; $i ++ ) { $fr = ord( $this->data[ $spos + 8 * $i + 2 ] ) | ord( $this->data[ $spos + 8 * $i + 3 ] ) << 8; $lr = ord( $this->data[ $spos + 8 * $i + 4 ] ) | ord( $this->data[ $spos + 8 * $i + 5 ] ) << 8; $fc = ord( $this->data[ $spos + 8 * $i + 6 ] ) | ord( $this->data[ $spos + 8 * $i + 7 ] ) << 8; $lc = ord( $this->data[ $spos + 8 * $i + 8 ] ) | ord( $this->data[ $spos + 8 * $i + 9 ] ) << 8; //$this->sheets[$this->sn]['mergedCells'][] = array($fr + 1, $fc + 1, $lr + 1, $lc + 1); if ( $lr - $fr > 0 ) { $this->sheets[ $this->sn ]['cellsInfo'][ $fr + 1 ][ $fc + 1 ]['rowspan'] = $lr - $fr + 1; } if ( $lc - $fc > 0 ) { $this->sheets[ $this->sn ]['cellsInfo'][ $fr + 1 ][ $fc + 1 ]['colspan'] = $lc - $fc + 1; } } //echo "Merged Cells $cellRanges $lr $fr $lc $fc\n"; break; case self::TYPE_RK: case self::TYPE_RK2: //echo 'self::TYPE_RK'."\n"; $row = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8; $column = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; $rknum = $this->_GetInt4d( $this->data, $spos + 6 ); $numValue = $this->_GetIEEE754( $rknum ); //echo $numValue." "; if ( $this->isDate( $spos ) ) { list( $string, $raw ) = $this->createDate( $numValue ); } else { $raw = $numValue; if ( isset( $this->columnsFormat[ $column + 1 ] ) ) { $this->curFormat = $this->columnsFormat[ $column + 1 ]; } $string = sprintf( $this->curFormat, $numValue * $this->multiplier ); //$this->addcell(RKRecord($r)); } $this->addcell( $row, $column, $string, $raw ); //echo "Type_RK $row $column $string $raw {$this->curformat}\n"; break; case self::TYPE_LABELSST: $row = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8; $column = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; // $xfindex = ord($this->_data[$spos + 4]) | ord($this->_data[$spos + 5]) << 8; $index = $this->_GetInt4d( $this->data, $spos + 6 ); //var_dump($this->sst); $this->addcell( $row, $column, $this->sst[ $index ] ); //echo "LabelSST $row $column $string\n"; break; case self::TYPE_MULRK: $row = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8; $colFirst = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; $colLast = ord( $this->data[ $spos + $length - 2 ] ) | ord( $this->data[ $spos + $length - 1 ] ) << 8; $columns = $colLast - $colFirst + 1; $tmppos = $spos + 4; for ( $i = 0; $i < $columns; $i ++ ) { $numValue = $this->_GetIEEE754( $this->_GetInt4d( $this->data, $tmppos + 2 ) ); if ( $this->isDate( $tmppos - 4 ) ) { list( $string, $raw ) = $this->createDate( $numValue ); } else { $raw = $numValue; if ( isset( $this->columnsFormat[ $colFirst + $i + 1 ] ) ) { $this->curFormat = $this->columnsFormat[ $colFirst + $i + 1 ]; } $string = sprintf( $this->curFormat, $numValue * $this->multiplier ); } //$rec['rknumbers'][$i]['xfindex'] = ord($rec['data'][$pos]) | ord($rec['data'][$pos+1]) << 8; $tmppos += 6; $this->addcell( $row, $colFirst + $i, $string, $raw ); //echo "MULRK $row ".($colFirst + $i)." $string\n"; } //MulRKRecord($r); // Get the individual cell records from the multiple record //$num = ; break; case self::TYPE_NUMBER: $row = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8; $column = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; $tmp = unpack( 'ddouble', $this->_substr( $this->data, $spos + 6, 8 ) ); // It machine machine dependent if ( $this->isDate( $spos ) ) { list( $string, $raw ) = $this->createDate( $tmp['double'] ); // $this->addcell(DateRecord($r, 1)); } else { //$raw = $tmp['']; if ( isset( $this->columnsFormat[ $column + 1 ] ) ) { $this->curFormat = $this->columnsFormat[ $column + 1 ]; } $raw = $this->createNumber( $spos ); $string = sprintf( $this->curFormat, $raw * $this->multiplier ); // $this->addcell(NumberRecord($r)); } $this->addcell( $row, $column, $string, $raw ); //echo "Number $row $column $string\n"; break; case self::TYPE_FORMULA: case self::TYPE_FORMULA2: $row = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8; $column = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; /* $byte6 = ord($this->_data[$spos + 6]); $byte12 = ord($this->_data[$spos + 12]); $byte13 = ord($this->_data[$spos + 13]); if ( $byte6 === 0 && $byte12 === 255 && $byte13 === 255 ) { //String formula. Result follows in a STRING record //echo "FORMULA $row $column Formula with a string
\n"; } else if ($byte6 === 1 && $byte12 === 255 && $byte13 === 255 ) { //Boolean formula. Result is in +2; 0=false,1=true } else if ($byte6 === 2 && $byte12 === 255 && $byte13 === 255) { //Error formula. Error code is in +2; } else if ( $byte6 === 3 && $byte12 === 255 && $byte13 === 255) { //Formula result is a null string. */ if ( ! ( ord( $this->data[ $spos + 6 ] ) < 4 && ord( $this->data[ $spos + 12 ] ) === 255 && ord( $this->data[ $spos + 13 ] ) === 255 ) ) { // result is a number, so first 14 bytes are just like a _NUMBER record $tmp = unpack( 'ddouble', $this->_substr( $this->data, $spos + 6, 8 ) ); // It machine machine dependent if ( $this->isDate( $spos ) ) { list( $string, $raw ) = $this->createDate( $tmp['double'] ); // $this->addcell(DateRecord($r, 1)); } else { //$raw = $tmp['']; if ( isset( $this->columnsFormat[ $column + 1 ] ) ) { $this->curFormat = $this->columnsFormat[ $column + 1 ]; } $raw = $this->createNumber( $spos ); $string = sprintf( $this->curFormat, $raw * $this->multiplier ); // $this->addcell(NumberRecord($r)); } $this->addcell( $row, $column, $string, $raw ); //echo "Number $row $column $string\n"; } break; case self::TYPE_BOOLERR: $row = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8; $column = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; $string = ord( $this->data[ $spos + 6 ] ); $this->addcell( $row, $column, $string ); //echo 'Type_BOOLERR '."\n"; break; case self::TYPE_ROW: case self::TYPE_DBCELL: case self::TYPE_MULBLANK: break; case self::TYPE_LABEL: $row = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8; $column = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8; $this->addcell( $row, $column, $this->_substr( $this->data, $spos + 8, ord( $this->data[ $spos + 6 ] ) | ord( $this->data[ $spos + 7 ] ) << 8 ) ); // $this->addcell(LabelRecord($r)); break; case self::TYPE_EOF: $cont = false; break; default: //echo ' unknown :'.base_convert($r['code'],10,16)."\n"; break; } $spos += $length; } if ( ! isset( $this->sheets[ $this->sn ]['numRows'] ) ) { $this->sheets[ $this->sn ]['numRows'] = $this->sheets[ $this->sn ]['maxrow']; } if ( ! isset( $this->sheets[ $this->sn ]['numCols'] ) ) { $this->sheets[ $this->sn ]['numCols'] = $this->sheets[ $this->sn ]['maxcol']; } return true; } //}}} //{{{ createDate() public function _GetIEEE754( $rknum ) { if ( ( $rknum & 0x02 ) !== 0 ) { $value = $rknum >> 2; } else { //mmp // first comment out the previously existing 7 lines of code here // $tmp = unpack("d", pack("VV", 0, ($rknum & 0xfffffffc))); // //$value = $tmp['']; // if (array_key_exists(1, $tmp)) { // $value = $tmp[1]; // } else { // $value = $tmp['']; // } // I got my info on IEEE754 encoding from // http://research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html // The RK format calls for using only the most significant 30 bits of the // 64 bit floating point value. The other 34 bits are assumed to be 0 // So, we use the upper 30 bits of $rknum as follows... $sign = ( $rknum & 0x80000000 ) >> 31; $exp = ( $rknum & 0x7ff00000 ) >> 20; $mantissa = ( 0x100000 | ( $rknum & 0x000ffffc ) ); $value = $mantissa / pow( 2, 20 - ( $exp - 1023 ) ); if ( $sign ) { $value = - 1 * $value; } //end of changes by mmp } if ( ( $rknum & 0x01 ) !== 0 ) { $value /= 100; } return $value; } public function isDate( $spos ) { //$xfindex = GetInt2d(, 4); $xfindex = ord( $this->data[ $spos + 4 ] ) | ord( $this->data[ $spos + 5 ] ) << 8; //echo 'check is date '.$xfindex.' '.$this->formatRecords['xfrecords'][$xfindex]['type']."\n"; //var_dump($this->formatRecords['xfrecords'][$xfindex]); if ( $this->formatRecords['xfrecords'][ $xfindex ]['type'] === 'date' ) { $this->curFormat = $this->formatRecords['xfrecords'][ $xfindex ]['format']; $this->recType = 'date'; return true; } if ( $this->formatRecords['xfrecords'][ $xfindex ]['type'] === 'number' ) { $this->curFormat = $this->formatRecords['xfrecords'][ $xfindex ]['format']; $this->recType = 'number'; if ( ( $xfindex === 0x9 ) || ( $xfindex === 0xa ) ) { $this->multiplier = 100; } } else { $this->curFormat = $this->defaultFormat; $this->recType = 'unknown'; } return false; } /** * Convert the raw Excel date into a human readable format * * Dates in Excel are stored as number of seconds from an epoch. On * Windows, the epoch is 30/12/1899 and on Mac it's 01/01/1904 * * @param integer $timevalue The raw Excel value to convert * * @return array First element is the converted date, the second element is number a unix timestamp */ public function createDate( $timevalue ) { // $offset = ($timeoffset===null)? date('Z') : $timeoffset * 3600; if ($timevalue > 1) { $timevalue -= ( $this->nineteenFour ? 24107 : 25569 ); } $ts = round($timevalue * 24 * 3600); $string = $this->datetimeFormat ? gmdate( $this->datetimeFormat, $ts ) : gmdate( $this->curFormat, $ts ); return array( $string, $ts ); } public function addcell( $row, $col, $string, $raw = '' ) { //echo "ADD cel $row-$col $string\n"; $this->sheets[ $this->sn ]['maxrow'] = max( $this->sheets[ $this->sn ]['maxrow'], $row + $this->rowoffset ); $this->sheets[ $this->sn ]['maxcol'] = max( $this->sheets[ $this->sn ]['maxcol'], $col + $this->colOffset ); $this->sheets[ $this->sn ]['cells'][ $row + $this->rowoffset ][ $col + $this->colOffset ] = $string; if ( $raw ) { $this->sheets[ $this->sn ]['cellsInfo'][ $row + $this->rowoffset ][ $col + $this->colOffset ]['raw'] = $raw; } if ( isset( $this->recType ) ) { $this->sheets[ $this->sn ]['cellsInfo'][ $row + $this->rowoffset ][ $col + $this->colOffset ]['type'] = $this->recType; } } public function createNumber( $spos ) { $rknumhigh = $this->_GetInt4d( $this->data, $spos + 10 ); $rknumlow = $this->_GetInt4d( $this->data, $spos + 6 ); //for ($i=0; $i<8; $i++) { echo ord($this->_data[$i+$spos+6]) . " "; } echo "
"; $sign = ( $rknumhigh & 0x80000000 ) >> 31; $exp = ( $rknumhigh & 0x7ff00000 ) >> 20; $mantissa = ( 0x100000 | ( $rknumhigh & 0x000fffff ) ); $mantissalow1 = ( $rknumlow & 0x80000000 ) >> 31; $mantissalow2 = ( $rknumlow & 0x7fffffff ); $value = $mantissa / pow( 2, 20 - ( $exp - 1023 ) ); if ( $mantissalow1 !== 0 ) { $value += 1 / pow( 2, 21 - ( $exp - 1023 ) ); } $value += $mantissalow2 / pow( 2, 52 - ( $exp - 1023 ) ); //echo "Sign = $sign, Exp = $exp, mantissahighx = $mantissa, mantissalow1 = $mantissalow1, mantissalow2 = $mantissalow2
\n"; if ( $sign ) { $value = - 1 * $value; } return $value; } /** * Set the encoding method * * @param string $encoding Encoding to use * * @access public */ public function setOutputEncoding( $encoding ) { $this->defaultEncoding = $encoding; } public function setRowColOffset( $iOffset ) { $this->rowoffset = $iOffset; $this->colOffset = $iOffset; } /** * Set the default number format * * @access public * * @param string $sFormat Default format */ public function setDefaultFormat( $sFormat ) { $this->defaultFormat = $sFormat; } /** * Force a column to use a certain format * * @access public * * @param integer $column Column number * @param string $sFormat Format */ public function setColumnFormat( $column, $sFormat ) { $this->columnsFormat[ $column ] = $sFormat; } private function _strlen( $str ) { return (ini_get('mbstring.func_overload') & 2) ? mb_strlen($str , '8bit') : strlen($str); } private function _strpos( $haystack, $needle, $offset = 0 ) { return (ini_get('mbstring.func_overload') & 2) ? mb_strpos( $haystack, $needle, $offset , '8bit') : strpos($haystack, $needle, $offset); } private function _substr( $str, $start, $length = null ) { return (ini_get('mbstring.func_overload') & 2) ? mb_substr( $str, $start, ($length === null) ? mb_strlen($str,'8bit') : $length, '8bit') : substr($str, $start, ($length === null) ? strlen($str) : $length ); } } /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */