From 42558401ef6915cb036c2b0a0a265c400823eed7 Mon Sep 17 00:00:00 2001 From: Sergey Shuchkin Date: Wed, 4 Nov 2020 01:42:52 +0600 Subject: [PATCH] Multiple sheets support and class ready for extends now --- README.md | 22 ++-- src/SimpleXLSXGen.php | 243 ++++++++++++++++++++---------------------- 2 files changed, 130 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index c2d10bf..0c151bf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SimpleXLSXGen class 0.9.21 (Official) +# SimpleXLSXGen class 0.9.22 (Official) [](https://www.patreon.com/shuchkin) [](https://github.com/shuchkin/simplexlsxgen/blob/master/license.md) [](https://github.com/shuchkin/simplexlsxgen/stargazers) [](https://github.com/shuchkin/simplexlsxgen/network) [](https://github.com/shuchkin/simplexlsxgen/issues) Export data to Excel XLSX file. PHP XLSX generator. No external tools and libraries.
@@ -16,12 +16,9 @@ $books = [ [908606664, 'Slinky Malinki', 'Lynley Dodd', 'Mallinson Rendel', 'NZ'] ]; $xlsx = SimpleXLSXGen::fromArray( $books ); -$xlsx->saveAs('books.xlsx'); +$xlsx->saveAs('books.xlsx'); // or downloadAs('books.xlsx') ``` ![XLSX screenshot](books.png) -``` -// SimpleXLSXGen::download() or SimpleXSLSXGen::downloadAs('table.xlsx'); -``` ## Installation The recommended way to install this library is [through Composer](https://getcomposer.org). @@ -49,7 +46,19 @@ $data = [ SimpleXLSXGen::fromArray( $data )->saveAs('datatypes.xlsx'); ``` ![XLSX screenshot](datatypes.png) - +### Fluid examples +```php +SimpleXLSXGen::fromArray( $books )->downloadAs('table.xlsx'); // output to browser for download +SimpleXLSXGen::fromArray( $books )->addSheet( $books2 )->download(); // multiple sheets +(new SimpleXLSXGen)->addSheet( $books, 'Modern style')->save(); +``` +### Old school, multiple sheets +```php +$xlsx = new SimpleXLSXGen(); +$xlsx->addSheet( $books, 'Catalog 2021' ); +$xlsx->addSheet( $books2, 'Stephen King catalog'); +$xlsx->downloadAs('books_2021.xlsx'); +``` ### Debug ```php ini_set('error_reporting', E_ALL ); @@ -63,6 +72,7 @@ SimpleXLSXGen::fromArray( $data )->saveAs('debug.xlsx'); ## History +v0.9.22 (2020-11-04) Added multiple sheets support, thx [Savino59](https://github.com/Savino59), class ready for extend now
v0.9.21 (2020-10-17) Updated images
v0.9.20 (2020-10-04) Disable type detection if string started with chr(0)
v0.9.19 (2020-08-23) Numbers like SKU right aligned now
diff --git a/src/SimpleXLSXGen.php b/src/SimpleXLSXGen.php index de5c316..44806c8 100644 --- a/src/SimpleXLSXGen.php +++ b/src/SimpleXLSXGen.php @@ -8,16 +8,15 @@ class SimpleXLSXGen { public $curSheet; - private $sheets; - private $template; - private $SI, $SI_KEYS; + protected $sheets; + protected $template; + protected $SI, $SI_KEYS; public function __construct() { - $this->curSheet = 0; - $this->sheets[0]['rows'] = []; - $this->sheets[0]['Name'] = 'Sheet1'; - $this->SI = []; // sharedStrings index & keys - $this->SI_KEYS = []; + $this->curSheet = -1; + $this->sheets = [ ['name' => 'Sheet1', 'rows' => [] ] ]; + $this->SI = []; // sharedStrings index + $this->SI_KEYS = []; // & keys $this->template = [ '[Content_Types].xml' => ' @@ -83,35 +82,22 @@ class SimpleXLSXGen { // 01001200 // Простой шаблонБудем делать генератор } - - public function setSheet($idx, $name = null) { - if (!isset($this->sheets[$idx])) { - $this->sheets[$idx] = []; - $this->sheets[$idx]['rows'] = []; - } - $this->curSheet = $idx; - if ($name) - $this->sheets[$idx]['Name'] = $name; - else { - if (!isset($this->sheets[$idx]['Name'])) { - $this->sheets[$idx]['Name'] = 'Sheet'.$idx; - } - } - return $this->sheets[$idx]['Name']; + public static function fromArray( array $rows, $sheetName = null ) { + $xlsx = new static(); + return $xlsx->addSheet( $rows, $sheetName ); } - public static function fromArray( array $rows ) { - $xlsx = new self(); - $xlsx->setRows( $rows ); - return $xlsx; - } + public function addSheet( array $rows, $name = null ) { + $this->curSheet++; + + $this->sheets[$this->curSheet] = ['name' => $name ?: 'Sheet'.($this->curSheet+1)]; - public function setRows( $rows ) { if ( is_array( $rows ) && isset( $rows[0] ) && is_array($rows[0]) ) { - $this->sheets[$this->curSheet]['rows']=$rows; + $this->sheets[$this->curSheet]['rows'] = $rows; } else { - $this->sheets[$this->curSheet]['rows']= []; + $this->sheets[$this->curSheet]['rows'] = []; } + return $this; } public function __toString() { @@ -120,7 +106,7 @@ class SimpleXLSXGen { return ''; } - if ( ! $this->_generate( $fh ) ) { + if ( ! $this->_write( $fh ) ) { fclose( $fh ); return ''; } @@ -135,7 +121,7 @@ class SimpleXLSXGen { if (!$fh) { return false; } - if ( !$this->_generate($fh) ) { + if ( !$this->_write($fh) ) { fclose($fh); return false; } @@ -154,7 +140,7 @@ class SimpleXLSXGen { return false; } - if ( !$this->_generate( $fh )) { + if ( !$this->_write( $fh )) { fclose( $fh ); return false; } @@ -176,7 +162,96 @@ class SimpleXLSXGen { return true; } - private function _saveIt($fh, &$cdrec, $cfilename, &$data) { + protected function _write( $fh ) { + + + $dirSignatureE= "\x50\x4b\x05\x06"; // end of central dir signature + $zipComments = 'Generated by '.__CLASS__.' PHP class, thanks sergey.shuchkin@gmail.com'; + + if (!$fh) { + return false; + } + + $cdrec = ''; // central directory content + $entries= 0; // number of zipped files + $cnt_sheets = count( $this->sheets ); + + foreach ($this->template as $cfilename => $template ) { + if ( $cfilename === '[Content_Types].xml' ) { + $s = ''; + for ( $i = 0; $i < $cnt_sheets; $i++) { + $s .= ''; + } + $template = str_replace('{SHEETS}', $s, $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } + elseif ( $cfilename === 'xl/_rels/workbook.xml.rels' ) { + $s = ''; + for ( $i = 0; $i < $cnt_sheets; $i++) { + $s .= '\n"; + } + $s .= ''; + $template = str_replace('{SHEETS}', $s, $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } + elseif ( $cfilename === 'xl/workbook.xml' ) { + $s = ''; + foreach ( $this->sheets as $k => $v ) { + $s .= ''; + } + $template = str_replace('{SHEETS}', $s, $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } + elseif ( $cfilename === 'docProps/core.xml' ) { + $template = str_replace('{DATE}', gmdate('Y-m-d\TH:i:s\Z'), $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } elseif ( $cfilename === 'xl/sharedStrings.xml' ) { + if (!count($this->SI)) { + $this->SI[] = 'No Data'; + } + $si_cnt = count($this->SI); + $this->SI = ''.implode("\r\n", $this->SI).''; + $template = str_replace(['{CNT}', '{STRINGS}'], [ $si_cnt, $this->SI ], $template ); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } elseif ( $cfilename === 'xl/worksheets/sheet1.xml' ) { + foreach ( $this->sheets as $k => $v ) { + $filename = 'xl/worksheets/sheet'.($k+1).'.xml'; + $xml = $this->_sheetToXML($this->sheets[$k], $template); + $this->_writeEntry($fh, $cdrec, $filename, $xml ); + $entries++; + } + $xml = null; + } + else { + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } + } + $before_cd = ftell($fh); + fwrite($fh, $cdrec); + + // end of central dir + fwrite($fh, $dirSignatureE); + fwrite($fh, pack('v', 0)); // number of this disk + fwrite($fh, pack('v', 0)); // number of the disk with the start of the central directory + fwrite($fh, pack('v', $entries)); // total # of entries "on this disk" + fwrite($fh, pack('v', $entries)); // total # of entries overall + fwrite($fh, pack('V', mb_strlen($cdrec,'8bit'))); // size of central dir + fwrite($fh, pack('V', $before_cd)); // offset to start of central dir + fwrite($fh, pack('v', mb_strlen($zipComments,'8bit'))); // .zip file comment length + fwrite($fh, $zipComments); + + return true; + } + + protected function _writeEntry($fh, &$cdrec, $cfilename, $data) { $zipSignature = "\x50\x4b\x03\x04"; // local file header signature $dirSignature = "\x50\x4b\x01\x02"; // central dir header signature @@ -215,7 +290,6 @@ class SimpleXLSXGen { $e['offset'] = ftell($fh); - /** @noinspection DisconnectedForeachInstructionInspection */ fwrite($fh, $zipSignature); fwrite($fh, pack('s', $e['vneeded'])); // version_needed fwrite($fh, pack('s', $e['bitflag'])); // general_bit_flag @@ -226,7 +300,6 @@ class SimpleXLSXGen { fwrite($fh, pack('I', $e['comsize'])); // compressed_size fwrite($fh, pack('I', $e['uncsize'])); // uncompressed_size fwrite($fh, pack('s', mb_strlen($cfilename, '8bit'))); // file_name_length - /** @noinspection DisconnectedForeachInstructionInspection */ fwrite($fh, pack('s', 0)); // extra_field_length fwrite($fh, $cfilename); // file_name // ignoring extra_field @@ -257,7 +330,7 @@ class SimpleXLSXGen { $cdrec .= $e['comments']; } - private function worksheetEncode(&$sheet, &$data) { + protected function _sheetToXML(&$sheet, $template) { $COLS = []; $ROWS = []; @@ -343,7 +416,7 @@ class SimpleXLSXGen { } $row .= '' - .($ct === 'inlineStr' ? ''.$cv.'' : '' . $cv . '')."\r\n"; + .($ct === 'inlineStr' ? ''.$cv.'' : '' . $cv . '')."\r\n"; } $ROWS[] = $row . "\r\n"; } @@ -357,96 +430,8 @@ class SimpleXLSXGen { $REF = 'A1:A1'; } return str_replace(['{REF}','{COLS}','{ROWS}'], - [ $REF, implode("\r\n", $COLS), implode("\r\n",$ROWS) ], - $data ); - } - - private function _generate( $fh ) { - - - $dirSignatureE= "\x50\x4b\x05\x06"; // end of central dir signature - $zipComments = 'Generated by '.__CLASS__.' PHP class, thanks sergey.shuchkin@gmail.com'; - - if (!$fh) { - return false; - } - - $cdrec = ''; // central directory content - $entries= 0; // number of zipped files - - foreach ($this->template as $cfilename => $data ) { - if ( $cfilename === '[Content_Types].xml' ) { - $s = ''; - for ($i=0; $i < count($this->sheets); $i++) { - $s .= ''; - } - $data = str_replace('{SHEETS}', $s, $data); - $this->_saveIt($fh, $cdrec, $cfilename, $data); - $entries++; - } - elseif ( $cfilename === 'xl/_rels/workbook.xml.rels' ) { - $s = ''; - for ($i=0; $i < count($this->sheets); $i++) { - $s .= '\n"; - } - $s .= ''; - $data = str_replace('{SHEETS}', $s, $data); - $this->_saveIt($fh, $cdrec, $cfilename, $data); - $entries++; - } - elseif ( $cfilename === 'xl/workbook.xml' ) { - $s = ''; - for ($i=0; $i < count($this->sheets); $i++) { - $s .= ''; - } - $data = str_replace('{SHEETS}', $s, $data); - $this->_saveIt($fh, $cdrec, $cfilename, $data); - $entries++; - } - elseif ( $cfilename === 'docProps/core.xml' ) { - $data = str_replace('{DATE}', gmdate('Y-m-d\TH:i:s\Z'), $data); - $this->_saveIt($fh, $cdrec, $cfilename, $data); - $entries++; - } elseif ( $cfilename === 'xl/sharedStrings.xml' ) { - if (!count($this->SI)) { - $this->SI[] = 'No Data'; - } - $si_cnt = count($this->SI); - $this->SI = ''.implode("\r\n", $this->SI).''; - $data = str_replace(['{CNT}', '{STRINGS}'], [ $si_cnt, $this->SI ], $data ); - $this->_saveIt($fh, $cdrec, $cfilename, $data); - $entries++; - } elseif ( $cfilename === 'xl/worksheets/sheet1.xml' ) { - for ($i=0; $i < count($this->sheets); $i++) { - $filename = 'xl/worksheets/sheet'.($i+1).'.xml'; - $data1 = $this->worksheetEncode($this->sheets[$i], $data); - $this->_saveIt($fh, $cdrec, $filename, $data1); - $entries++; - } - $data1=null; - } - else { - $this->_saveIt($fh, $cdrec, $cfilename, $data); - $entries++; - } - } - $before_cd = ftell($fh); - fwrite($fh, $cdrec); - - // end of central dir - fwrite($fh, $dirSignatureE); - fwrite($fh, pack('v', 0)); // number of this disk - fwrite($fh, pack('v', 0)); // number of the disk with the start of the central directory - fwrite($fh, pack('v', $entries)); // total # of entries "on this disk" - fwrite($fh, pack('v', $entries)); // total # of entries overall - fwrite($fh, pack('V', mb_strlen($cdrec,'8bit'))); // size of central dir - fwrite($fh, pack('V', $before_cd)); // offset to start of central dir - fwrite($fh, pack('v', mb_strlen($zipComments,'8bit'))); // .zip file comment length - fwrite($fh, $zipComments); - - return true; + [ $REF, implode("\r\n", $COLS), implode("\r\n",$ROWS) ], + $template ); } public function num2name($num) {