Multiple sheets support and class ready for extends now

This commit is contained in:
Sergey Shuchkin 2020-11-04 01:42:52 +06:00
parent 07d3e91c28
commit 42558401ef
2 changed files with 130 additions and 135 deletions

View File

@ -1,4 +1,4 @@
# SimpleXLSXGen class 0.9.21 (Official)
# SimpleXLSXGen class 0.9.22 (Official)
[<img src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.herokuapp.com%2Fshuchkin" />](https://www.patreon.com/shuchkin) [<img src="https://img.shields.io/github/license/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/blob/master/license.md) [<img src="https://img.shields.io/github/stars/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/stargazers) [<img src="https://img.shields.io/github/forks/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/network) [<img src="https://img.shields.io/github/issues/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/issues)
Export data to Excel XLSX file. PHP XLSX generator. No external tools and libraries.<br/>
@ -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<br/>
v0.9.21 (2020-10-17) Updated images<br/>
v0.9.20 (2020-10-04) Disable type detection if string started with chr(0)<br/>
v0.9.19 (2020-08-23) Numbers like SKU right aligned now<br/>

View File

@ -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' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
@ -83,35 +82,22 @@ class SimpleXLSXGen {
// <row r="1" spans="1:2" x14ac:dyDescent="0.35"><c r="A1" t="s"><v>0</v></c><c r="B1"><v>100</v></c></row><row r="2" spans="1:2" x14ac:dyDescent="0.35"><c r="A2" t="s"><v>1</v></c><c r="B2"><v>200</v></c></row>
// <si><t>Простой шаблон</t></si><si><t>Будем делать генератор</t></si>
}
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 .= '<Override PartName="/xl/worksheets/sheet'.($i+1).
'.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>';
}
$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 .= '<Relationship Id="rId'.($i+2).'" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"'.
' Target="worksheets/sheet'.($i+1).".xml\"/>\n";
}
$s .= '<Relationship Id="rId'.($i+2).'" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/></Relationships>';
$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 .= '<sheet name="' . $v['name'] . '" sheetId="' . ( $k + 1) . '" state="visible" r:id="rId' . ( $k + 2) . '"/>';
}
$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 = '<si><t>'.implode("</t></si>\r\n<si><t>", $this->SI).'</t></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 .= '<c r="' . $cname . '"'.($ct ? ' t="'.$ct.'"' : '').($cs ? ' s="'.$cs.'"' : '').'>'
.($ct === 'inlineStr' ? '<is><t>'.$cv.'</t></is>' : '<v>' . $cv . '</v>')."</c>\r\n";
.($ct === 'inlineStr' ? '<is><t>'.$cv.'</t></is>' : '<v>' . $cv . '</v>')."</c>\r\n";
}
$ROWS[] = $row . "</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 .= '<Override PartName="/xl/worksheets/sheet'.($i+1).
'.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>';
}
$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 .= '<Relationship Id="rId'.($i+2).'" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"'.
' Target="worksheets/sheet'.($i+1).".xml\"/>\n";
}
$s .= '<Relationship Id="rId'.($i+2).'" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/></Relationships>';
$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 .= '<sheet name="'.$this->sheets[$i]['Name'].'" sheetId="'.($i+1).'" state="visible" r:id="rId'.($i+2).'"/>';
}
$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 = '<si><t>'.implode("</t></si>\r\n<si><t>", $this->SI).'</t></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) {