diff --git a/CHANGELOG.md b/CHANGELOG.md
index c35763d..6b4f9a7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,12 @@
# Changelog
+
+## 1.3.10 (2022-12-14)
+* added borders `````` see colored [examples](https://github.com/shuchkin/simplexlsxgen#formatting)
+* added formulas ```SUM(B1:B10)``` see [examples](https://github.com/shuchkin/simplexlsxgen#data-types)
+* added internal links ```Go to page 2```
+* added custom number formats ``````
+* added 3 currencies ```$data = [ ['$100.23', '2000.00 €', '1200.30 ₽'] ];```
+
## 1.2.16 (2022-08-12)
* added `autoFilter( $range )`
```php
diff --git a/README.md b/README.md
index 3a05894..3deb84c 100644
--- a/README.md
+++ b/README.md
@@ -39,16 +39,22 @@ $data = [
['Integer', 123],
['Float', 12.35],
['Percent', '12%'],
+ ['Currency $', '$500.67'],
+ ['Currency €', '200 €'],
+ ['Currency ₽', '1200.30 ₽'],
+ ['Currency (other)', ''],
['Datetime', '2020-05-20 02:38:00'],
- ['Date','2020-05-20'],
- ['Time','02:38:00'],
+ ['Date', '2020-05-20'],
+ ['Time', '02:38:00'],
['Datetime PHP', new DateTime('2021-02-06 21:07:00')],
['String', 'Long UTF-8 String in autoresized column'],
+ ['Formula', 'SUM(B1:B2)'],
['Hyperlink', 'https://github.com/shuchkin/simplexlsxgen'],
['Hyperlink + Anchor', 'SimpleXLSXGen'],
- ['RAW string', "\0".'2020-10-04 16:02:00']
+ ['Internal link', 'Go to second page'],
+ ['RAW string', "\0" . '2020-10-04 16:02:00']
];
-Shuchkin\SimpleXLSXGen::fromArray( $data )->saveAs('datatypes.xlsx');
+SimpleXLSXGen::fromArray($data)->saveAs('datatypes.xlsx');
```
![XLSX screenshot](datatypes.png)
@@ -66,6 +72,9 @@ $data = [
['Green', ''],
['Bold Red Text', ''],
['Blue Text and Yellow Fill', ''],
+ ['Border color', ''],
+ ['Border style',''],
+ ['Border sides', ''],
['Left', '12345.67'],
['Center', '
12345.67'],
['Right', 'Right Text'],
@@ -77,10 +86,10 @@ $data = [
['MERGE CELLS MERGE CELLS MERGE CELLS MERGE CELLS MERGE CELLS', null],
['Word wrap', "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book"]
];
-SimpleXLSXGen::fromArray( $data )
- ->setDefaultFont( 'Courier New' )
- ->setDefaultFontSize( 14 )
- ->setColWidth(1, 35) // 1 - num column, 35 - size in chars
+SimpleXLSXGen::fromArray($data)
+ ->setDefaultFont('Courier New')
+ ->setDefaultFontSize(14)
+ ->setColWidth(1, 35)
->mergeCells('A20:B20')
->saveAs('styles_and_tags.xlsx');
```
diff --git a/datatypes.png b/datatypes.png
index 9dc3dba..9b0c822 100644
Binary files a/datatypes.png and b/datatypes.png differ
diff --git a/src/SimpleXLSXGen.php b/src/SimpleXLSXGen.php
index 8db5529..72b88c3 100644
--- a/src/SimpleXLSXGen.php
+++ b/src/SimpleXLSXGen.php
@@ -11,16 +11,22 @@ namespace Shuchkin;
* Export data to MS Excel. PHP XLSX generator
* Author: sergey.shuchkin@gmail.com
*/
-
-class SimpleXLSXGen {
+class SimpleXLSXGen
+{
public $curSheet;
protected $defaultFont;
protected $defaultFontSize;
protected $sheets;
protected $template;
- protected $XF, $XF_KEYS; // cellXfs
- protected $SI, $SI_KEYS; // shared strings
+ protected $NF; // numFmts
+ protected $NF_KEYS;
+ protected $XF; // cellXfs
+ protected $XF_KEYS;
+ protected $BR_STYLE;
+ protected $SI; // shared strings
+ protected $SI_KEYS;
+ protected $extLinkId;
const N_NORMAL = 0; // General
const N_INT = 1; // 0
const N_DEC = 2; // 0.00
@@ -28,6 +34,9 @@ class SimpleXLSXGen {
const N_PRECENT_DEC = 10; // 0.00%
const N_DATE = 14; // mm-dd-yy
const N_TIME = 20; // h:mm
+ const N_RUB = 164;
+ const N_DOLLAR = 165;
+ const N_EURO = 166;
const N_DATETIME = 22; // m/d/yy h:mm
const F_NORMAL = 0;
const F_HYPERLINK = 1;
@@ -40,7 +49,7 @@ class SimpleXLSXGen {
const FL_SOLID = 1; // solid
const FL_MEDIUM_GRAY = 2; // mediumGray
const FL_DARK_GRAY = 4; // darkGray
- const FL_LIGHT_GRAY = 8; // lightGray
+ const FL_LIGHT_GRAY = 8; // lightGray
const FL_GRAY_125 = 16; // gray125
const FL_COLOR = 32;
const A_DEFAULT = 0;
@@ -51,19 +60,64 @@ class SimpleXLSXGen {
const A_MIDDLE = 16;
const A_BOTTOM = 32;
const A_WRAPTEXT = 64;
+ const B_NONE = 0;
+ const B_THIN = 1;
+ const B_MEDIUM = 2;
+ //const
+ const B_DASHED = 3;
+ const B_DOTTED = 4;
+ const B_THICK = 5;
+ const B_DOUBLE = 6;
+ const B_HAIR = 7;
+ const B_MEDIUM_DASHED = 8;
+ const B_DASH_DOT = 9;
+ const B_MEDIUM_DASH_DOT = 10;
+ const B_DASH_DOT_DOT = 11;
+ const B_MEDIUM_DASH_DOT_DOT = 12;
+ const B_SLANT_DASH_DOT = 13;
- public function __construct() {
+ public function __construct()
+ {
$this->curSheet = -1;
$this->defaultFont = 'Calibri';
- $this->sheets = [ ['name' => 'Sheet1', 'rows' => [], 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => '' ] ];
- $this->SI = []; // sharedStrings index
+ $this->sheets = [['name' => 'Sheet1', 'rows' => [], 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => '']];
+ $this->extLinkId = 0;
+ $this->SI = []; // sharedStrings index
$this->SI_KEYS = []; // & keys
- $this->XF = [ // styles
- [self::N_NORMAL, self::A_DEFAULT, self::F_NORMAL, self::FL_NONE, 0, 0],
- [self::N_NORMAL, self::A_DEFAULT, self::F_NORMAL, self::FL_GRAY_125, 0, 0], // hack
+
+ // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_numFmts_topic_ID0E6KK6.html
+ $this->NF = [
+ self::N_RUB => '#,##0.00\ "₽"',
+ self::N_DOLLAR => '[$$-1]#,##0.00',
+ self::N_EURO => '#,##0.00\ [$€-1]'
];
- $this->XF_KEYS['0-0-0-0-0-0'] = 0; // & keys
- $this->XF_KEYS['0-0-0-16-0-0'] = 1;
+ $this->NF_KEYS = array_flip($this->NF);
+
+ $this->BR_STYLE = [
+ self::B_NONE => 'none',
+ self::B_THIN => 'thin',
+ self::B_MEDIUM => 'medium',
+ self::B_DASHED => 'dashed',
+ self::B_DOTTED => 'dotted',
+ self::B_THICK => 'thick',
+ self::B_DOUBLE => 'double',
+ self::B_HAIR => 'hair',
+ self::B_MEDIUM_DASHED => 'mediumDashed',
+ self::B_DASH_DOT => 'dashDot',
+ self::B_MEDIUM_DASH_DOT => 'mediumDashDot',
+ self::B_DASH_DOT_DOT => 'dashDotDot',
+ self::B_MEDIUM_DASH_DOT_DOT => 'mediumDashDotDot',
+ self::B_SLANT_DASH_DOT => 'slantDashDot'
+ ];
+
+
+ $this->XF = [ // styles 0 - num fmt, 1 - align, 2 - font, 3 - fill, 4 - font color, 5 - bgcolor, 6 - border
+ [self::N_NORMAL, self::A_DEFAULT, self::F_NORMAL, self::FL_NONE, 0, 0, ''],
+ [self::N_NORMAL, self::A_DEFAULT, self::F_NORMAL, self::FL_GRAY_125, 0, 0, ''], // hack
+ ];
+ $this->XF_KEYS[implode('-', $this->XF[0])] = 0; // & keys
+ $this->XF_KEYS[implode('-', $this->XF[1])] = 1;
+
$this->template = [
'_rels/.rels' => '
@@ -75,7 +129,7 @@ class SimpleXLSXGen {
'docProps/app.xml' => '
0
-'.__CLASS__.'',
+' . __CLASS__ . '',
'docProps/core.xml' => '
{DATE}
@@ -97,9 +151,10 @@ class SimpleXLSXGen {
{STRINGS}',
'xl/styles.xml' => '
+{NUMFMTS}
{FONTS}
{FILLS}
-
+{BORDERS}
{XF}
@@ -108,7 +163,7 @@ class SimpleXLSXGen {
',
'xl/workbook.xml' => '
-
+
{SHEETS}
',
'[Content_Types].xml' => '
@@ -128,38 +183,41 @@ class SimpleXLSXGen {
// 0100
1200
// Простой шаблонБудем делать генератор
}
- public static function fromArray( array $rows, $sheetName = null ) {
- return (new static())->addSheet( $rows, $sheetName );
+
+ public static function fromArray(array $rows, $sheetName = null)
+ {
+ return (new static())->addSheet($rows, $sheetName);
}
- public function addSheet( array $rows, $name = null ) {
+ public function addSheet(array $rows, $name = null)
+ {
$this->curSheet++;
- if ( $name === null ) { // autogenerated sheet names
- $name = 'Sheet'.($this->curSheet+1);
+ if ($name === null) { // autogenerated sheet names
+ $name = 'Sheet' . ($this->curSheet + 1);
} else {
$name = mb_substr($name, 0, 31);
$names = [];
- foreach( $this->sheets as $sh ) {
- $names[ mb_strtoupper( $sh['name']) ] = 1;
+ foreach ($this->sheets as $sh) {
+ $names[mb_strtoupper($sh['name'])] = 1;
}
- for( $i = 0; $i < 100; $i++ ) {
- $postfix = ' ('.$i.')';
+ for ($i = 0; $i < 100; $i++) {
+ $postfix = ' (' . $i . ')';
$new_name = ($i === 0) ? $name : $name . $postfix;
if (mb_strlen($new_name) > 31) {
- $new_name = mb_substr($name,0, 31-mb_strlen($postfix)) . $postfix;
+ $new_name = mb_substr($name, 0, 31 - mb_strlen($postfix)) . $postfix;
}
- $NEW_NAME = mb_strtoupper( $new_name );
- if ( !isset( $names[ $NEW_NAME ]) ) {
+ $NEW_NAME = mb_strtoupper($new_name);
+ if (!isset($names[$NEW_NAME])) {
$name = $new_name;
break;
}
}
}
- $this->sheets[$this->curSheet] = ['name' => $name, 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => '' ];
+ $this->sheets[$this->curSheet] = ['name' => $name, 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => ''];
- if ( isset( $rows[0] ) && is_array($rows[0]) ) {
+ if (isset($rows[0]) && is_array($rows[0])) {
$this->sheets[$this->curSheet]['rows'] = $rows;
} else {
$this->sheets[$this->curSheet]['rows'] = [];
@@ -167,28 +225,30 @@ class SimpleXLSXGen {
return $this;
}
- public function __toString() {
- $fh = fopen( 'php://memory', 'wb' );
- if ( ! $fh ) {
+ public function __toString()
+ {
+ $fh = fopen('php://memory', 'wb');
+ if (!$fh) {
return '';
}
- if ( ! $this->_write( $fh ) ) {
- fclose( $fh );
+ if (!$this->_write($fh)) {
+ fclose($fh);
return '';
}
- $size = ftell( $fh );
- fseek( $fh, 0);
+ $size = ftell($fh);
+ fseek($fh, 0);
- return (string) fread( $fh, $size );
+ return (string)fread($fh, $size);
}
- public function saveAs( $filename ) {
- $fh = fopen( $filename, 'wb' );
+ public function saveAs($filename)
+ {
+ $fh = fopen($filename, 'wb');
if (!$fh) {
return false;
}
- if ( !$this->_write($fh) ) {
+ if (!$this->_write($fh)) {
fclose($fh);
return false;
}
@@ -197,183 +257,256 @@ class SimpleXLSXGen {
return true;
}
- public function download() {
- return $this->downloadAs( gmdate('YmdHi') . '.xlsx' );
+ public function download()
+ {
+ return $this->downloadAs(gmdate('YmdHi') . '.xlsx');
}
- public function downloadAs( $filename ) {
- $fh = fopen('php://memory','wb');
+ public function downloadAs($filename)
+ {
+ $fh = fopen('php://memory', 'wb');
if (!$fh) {
return false;
}
- if ( !$this->_write( $fh )) {
- fclose( $fh );
+ if (!$this->_write($fh)) {
+ fclose($fh);
return false;
}
$size = ftell($fh);
header('Content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
- header('Content-Disposition: attachment; filename="'.$filename.'"');
- header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T' , time() ));
- header('Content-Length: '.$size);
+ header('Content-Disposition: attachment; filename="' . $filename . '"');
+ header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', time()));
+ header('Content-Length: ' . $size);
- while( ob_get_level() ) {
+ while (ob_get_level()) {
ob_end_clean();
}
- fseek($fh,0);
- fpassthru( $fh );
+ fseek($fh, 0);
+ fpassthru($fh);
fclose($fh);
return true;
}
- protected function _write( $fh ) {
+ 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';
+ $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 );
+ $cdrec = ''; // central directory content
+ $entries = 0; // number of zipped files
+ $cnt_sheets = count($this->sheets);
- foreach ($this->template as $cfilename => $template ) {
- if ( $cfilename === 'xl/_rels/workbook.xml.rels' ) {
+ foreach ($this->template as $cfilename => $template) {
+ if ($cfilename === 'xl/_rels/workbook.xml.rels') {
$s = '';
- for ( $i = 0; $i < $cnt_sheets; $i++) {
- $s .= '\n";
+ for ($i = 0; $i < $cnt_sheets; $i++) {
+ $s .= '\n";
}
- $s .= '';
+ $s .= '';
$template = str_replace('{SHEETS}', $s, $template);
$this->_writeEntry($fh, $cdrec, $cfilename, $template);
$entries++;
- } elseif ( $cfilename === 'xl/workbook.xml' ) {
+ } elseif ($cfilename === 'xl/workbook.xml') {
$s = '';
- foreach ( $this->sheets as $k => $v ) {
- $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' ) {
+ } 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' ) {
+ } elseif ($cfilename === 'xl/sharedStrings.xml') {
if (!count($this->SI)) {
$this->SI[] = 'No Data';
}
$si_cnt = count($this->SI);
- $si = ''.implode("\r\n", $this->SI).'';
- $template = str_replace(['{CNT}', '{STRINGS}'], [ $si_cnt, $si ], $template );
+ $si = '' . implode("\r\n", $this->SI) . '';
+ $template = str_replace(['{CNT}', '{STRINGS}'], [$si_cnt, $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';
+ } elseif ($cfilename === 'xl/worksheets/sheet1.xml') {
+ foreach ($this->sheets as $k => $v) {
+ $filename = 'xl/worksheets/sheet' . ($k + 1) . '.xml';
$xml = $this->_sheetToXML($k, $template);
- $this->_writeEntry($fh, $cdrec, $filename, $xml );
+ $this->_writeEntry($fh, $cdrec, $filename, $xml);
$entries++;
}
$xml = null;
- } elseif ( $cfilename === 'xl/worksheets/_rels/sheet1.xml.rels' ) {
- foreach ( $this->sheets as $k => $v ) {
- if ( count($v['hyperlinks'])) {
+ } elseif ($cfilename === 'xl/worksheets/_rels/sheet1.xml.rels') {
+ foreach ($this->sheets as $k => $v) {
+ if ($this->extLinkId) {
$RH = [];
- $filename = 'xl/worksheets/_rels/sheet' . ( $k + 1 ) . '.xml.rels';
- foreach ( $v['hyperlinks'] as $h ) {
- $RH[] = '';
+ $filename = 'xl/worksheets/_rels/sheet' . ($k + 1) . '.xml.rels';
+ foreach ($v['hyperlinks'] as $h) {
+ if ($h['ID']) {
+ $RH[] = '';
+ }
}
- $xml = str_replace( '{HYPERLINKS}', implode( "\r\n", $RH ), $template );
- $this->_writeEntry( $fh, $cdrec, $filename, $xml );
+ $xml = str_replace('{HYPERLINKS}', implode("\r\n", $RH), $template);
+ $this->_writeEntry($fh, $cdrec, $filename, $xml);
$entries++;
}
}
$xml = null;
- } elseif ( $cfilename === '[Content_Types].xml' ) {
+ } elseif ($cfilename === '[Content_Types].xml') {
$TYPES = [''];
- foreach ( $this->sheets as $k => $v) {
- $TYPES[] = '';
- if (count( $v['hyperlinks'])) {
- $TYPES[] = '';
+ foreach ($this->sheets as $k => $v) {
+ $TYPES[] = '';
+ if ($this->extLinkId) {
+ $TYPES[] = '';
}
}
$template = str_replace('{TYPES}', implode("\r\n", $TYPES), $template);
$this->_writeEntry($fh, $cdrec, $cfilename, $template);
$entries++;
- } elseif ( $cfilename === 'xl/styles.xml' ) {
- $XF = $FONTS = $F_KEYS = $FILLS = $FL_KEYS = [];
-// print_r( $this->XF );
- foreach( $this->XF as $xf ) {
- // 0 - num fmt, 1 - align, 2 - font, 3 - fill, 4 - font color, 5 - bgcolor
- // fonts
- $F_KEY = $xf[2].'-'.$xf[4];
- if ( isset($F_KEYS[ $F_KEY ]) ) {
- $F_ID = $F_KEYS[ $F_KEY ];
- } else {
- $F_ID = $F_KEYS[ $F_KEY ] = count( $FONTS );
+ } elseif ($cfilename === 'xl/styles.xml') {
+ $NF = $XF = $FONTS = $F_KEYS = $FILLS = $FL_KEYS = [];
+ $BR = [''];
+ $BR_KEYS = [0 => 0];
- $FONTS[] = ''
- . ( $this->defaultFontSize ? '' : '' )
- .( $xf[2] & self::F_BOLD ? '' : '')
- .( $xf[2] & self::F_ITALIC ? '' : '')
- .( $xf[2] & self::F_UNDERLINE ? '' : '')
- .( $xf[2] & self::F_STRIKE ? '' : '')
- .( $xf[2] & self::F_HYPERLINK ? '' : '')
- .( $xf[2] & self::F_COLOR ? '' : '')
- .'';
+ foreach ($this->NF as $k => $v) {
+ $NF[] = '';
+ }
+
+ foreach ($this->XF as $xf) {
+ // 0 - num fmt, 1 - align, 2 - font, 3 - fill, 4 - font color, 5 - bgcolor, 6 - border
+ // fonts
+ $F_KEY = $xf[2] . '-' . $xf[4];
+ if (isset($F_KEYS[$F_KEY])) {
+ $F_ID = $F_KEYS[$F_KEY];
+ } else {
+ $F_ID = $F_KEYS[$F_KEY] = count($FONTS);
+
+ $FONTS[] = ''
+ . ($this->defaultFontSize ? '' : '')
+ . ($xf[2] & self::F_BOLD ? '' : '')
+ . ($xf[2] & self::F_ITALIC ? '' : '')
+ . ($xf[2] & self::F_UNDERLINE ? '' : '')
+ . ($xf[2] & self::F_STRIKE ? '' : '')
+ . ($xf[2] & self::F_HYPERLINK ? '' : '')
+ . ($xf[2] & self::F_COLOR ? '' : '')
+ . '';
}
// fills
- $FL_KEY = $xf[3].'-'.$xf[5];
+ $FL_KEY = $xf[3] . '-' . $xf[5];
if (isset($FL_KEYS[$FL_KEY])) {
- $FL_ID = $FL_KEYS[ $FL_KEY ];
+ $FL_ID = $FL_KEYS[$FL_KEY];
} else {
- $FL_ID = $FL_KEYS[ $FL_KEY ] = count($FILLS);
+ $FL_ID = $FL_KEYS[$FL_KEY] = count($FILLS);
$FILLS[] = '' : ' />')
- .'';
+ . ($xf[3] === 0 ? 'none' : '')
+ . ($xf[3] & self::FL_SOLID ? 'solid' : '')
+ . ($xf[3] & self::FL_MEDIUM_GRAY ? 'mediumGray' : '')
+ . ($xf[3] & self::FL_DARK_GRAY ? 'darkGray' : '')
+ . ($xf[3] & self::FL_LIGHT_GRAY ? 'lightGray' : '')
+ . ($xf[3] & self::FL_GRAY_125 ? 'gray125' : '')
+ . '"'
+ . ($xf[3] & self::FL_COLOR ? '>' : ' />')
+ . '';
}
$align = ($xf[1] & self::A_LEFT ? ' horizontal="left"' : '')
- .($xf[1] & self::A_RIGHT ? ' horizontal="right"' : '')
- .($xf[1] & self::A_CENTER ? ' horizontal="center"' : '')
- .($xf[1] & self::A_TOP ? ' vertical="top"' : '')
- .($xf[1] & self::A_MIDDLE ? ' vertical="center"' : '')
- .($xf[1] & self::A_BOTTOM ? ' vertical="bottom"' : '')
- .($xf[1] & self::A_WRAPTEXT ? ' wrapText="1"' : '');
+ . ($xf[1] & self::A_RIGHT ? ' horizontal="right"' : '')
+ . ($xf[1] & self::A_CENTER ? ' horizontal="center"' : '')
+ . ($xf[1] & self::A_TOP ? ' vertical="top"' : '')
+ . ($xf[1] & self::A_MIDDLE ? ' vertical="center"' : '')
+ . ($xf[1] & self::A_BOTTOM ? ' vertical="bottom"' : '')
+ . ($xf[1] & self::A_WRAPTEXT ? ' wrapText="1"' : '');
- $XF[] = ' 0 ? ' applyNumberFormat="1"' : '')
- .($F_ID > 0 ? ' applyFont="1"' : '')
- .($FL_ID > 0 ? ' applyFill="1"' : '')
- .($align ? ' applyAlignment="1">' : '/>');
+ // border
+ $BR_ID = 0;
+ if ($xf[6] !== '') {
+ $b = $xf[6];
+ if (isset($BR_KEYS[$b])) {
+ $BR_ID = $BR_KEYS[$b];
+ } else {
+ $BR_ID = count($BR_KEYS);
+ $BR_KEYS[$b] = $BR_ID;
+ $border = '';
+ $ba = explode(' ', $b);
+ if (!isset($ba[1])) {
+ $ba[] = $ba[0];
+ $ba[] = $ba[0];
+ $ba[] = $ba[0];
+ }
+ if (!isset($ba[4])) { // diagonal
+ $ba[] = 'none';
+ }
+ $sides = [ 'left' => 3, 'right' => 1, 'top' => 0, 'bottom' => 2, 'diagonal' => 4];
+ foreach ($sides as $side => $idx) {
+ $s = '';
+ $c = '';
+
+ $va = explode('#', $ba[$idx]);
+ if (isset($va[1])) {
+ $s = $va[0] === '' ? 'thin' : $va[0];
+ $c = $va[1];
+ } elseif (in_array($va[0], $this->BR_STYLE, true)) {
+ $s = $va[0];
+ } else {
+ $s = 'thin';
+ $c = $va[0];
+ }
+ if (strlen($c) === 6) {
+ $c = 'FF' . $c;
+ }
+
+ if ($s && $s !== 'none') {
+ $border .= '<' . $side . ' style="' . $s . '">'
+ . ''
+ . '' . $side . '>';
+ } else {
+ $border .= '<' . $side . '/>';
+ }
+
+ }
+ $border .= '';
+ $BR[] = $border;
+ }
+ }
+
+
+ $XF[] = ' 0 ? ' applyNumberFormat="1"' : '')
+ . ($F_ID > 0 ? ' applyFont="1"' : '')
+ . ($FL_ID > 0 ? ' applyFill="1"' : '')
+ . ($BR_ID > 0 ? ' applyBorder="1"' : '')
+ . ($align ? ' applyAlignment="1">' : '/>');
}
- // wrap collections
- array_unshift( $XF, '');
- $XF[] = '';
- array_unshift($FONTS, '');
- $FONTS[] = '';
- array_unshift($FILLS, '');
- $FILLS[] = '';
- $template = str_replace(['{FONTS}','{XF}','{FILLS}'], [implode("\r\n", $FONTS), implode("\r\n", $XF), implode("\r\n", $FILLS)], $template);
+
+ // wrap collections
+ array_unshift($NF, '');
+ $NF[] = '';
+ array_unshift($XF, '');
+ $XF[] = '';
+ array_unshift($FONTS, '');
+ $FONTS[] = '';
+ array_unshift($FILLS, '');
+ $FILLS[] = '';
+ array_unshift($BR, '');
+ $BR[] = '';
+
+ $template = str_replace(
+ ['{NUMFMTS}', '{FONTS}', '{XF}', '{FILLS}', '{BORDERS}'],
+ [implode("\r\n", $NF), implode("\r\n", $FONTS), implode("\r\n", $XF), implode("\r\n", $FILLS), implode("\r\n", $BR)],
+ $template);
$this->_writeEntry($fh, $cdrec, $cfilename, $template);
$entries++;
} else {
@@ -390,15 +523,16 @@ class SimpleXLSXGen {
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', 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, pack('v', mb_strlen($zipComments, '8bit'))); // .zip file comment length
fwrite($fh, $zipComments);
return true;
}
- protected function _writeEntry($fh, &$cdrec, $cfilename, $data) {
+ 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
@@ -406,29 +540,29 @@ class SimpleXLSXGen {
$e['uncsize'] = mb_strlen($data, '8bit');
// if data to compress is too small, just store it
- if($e['uncsize'] < 256){
+ if ($e['uncsize'] < 256) {
$e['comsize'] = $e['uncsize'];
$e['vneeded'] = 10;
$e['cmethod'] = 0;
$zdata = $data;
- } else{ // otherwise, compress it
+ } else { // otherwise, compress it
$zdata = gzcompress($data);
- $zdata = substr(substr($zdata, 0, - 4 ), 2); // fix crc bug (thanks to Eric Mueller)
+ $zdata = substr(substr($zdata, 0, -4), 2); // fix crc bug (thanks to Eric Mueller)
$e['comsize'] = mb_strlen($zdata, '8bit');
$e['vneeded'] = 10;
$e['cmethod'] = 8;
}
$e['bitflag'] = 0;
- $e['crc_32'] = crc32($data);
+ $e['crc_32'] = crc32($data);
// Convert date and time to DOS Format, and set then
- $lastmod_timeS = str_pad(decbin(date('s')>=32?date('s')-32:date('s')), 5, '0', STR_PAD_LEFT);
- $lastmod_timeM = str_pad(decbin(date('i')), 6, '0', STR_PAD_LEFT);
- $lastmod_timeH = str_pad(decbin(date('H')), 5, '0', STR_PAD_LEFT);
- $lastmod_dateD = str_pad(decbin(date('d')), 5, '0', STR_PAD_LEFT);
- $lastmod_dateM = str_pad(decbin(date('m')), 4, '0', STR_PAD_LEFT);
- $lastmod_dateY = str_pad(decbin(date('Y')-1980), 7, '0', STR_PAD_LEFT);
+ $lastmod_timeS = str_pad(decbin(date('s') >= 32 ? date('s') - 32 : date('s')), 5, '0', STR_PAD_LEFT);
+ $lastmod_timeM = str_pad(decbin(date('i')), 6, '0', STR_PAD_LEFT);
+ $lastmod_timeH = str_pad(decbin(date('H')), 5, '0', STR_PAD_LEFT);
+ $lastmod_dateD = str_pad(decbin(date('d')), 5, '0', STR_PAD_LEFT);
+ $lastmod_dateM = str_pad(decbin(date('m')), 4, '0', STR_PAD_LEFT);
+ $lastmod_dateY = str_pad(decbin(date('Y') - 1980), 7, '0', STR_PAD_LEFT);
# echo "ModTime: $lastmod_timeS-$lastmod_timeM-$lastmod_timeH (".date("s H H").")\n";
# echo "ModDate: $lastmod_dateD-$lastmod_dateM-$lastmod_dateY (".date("d m Y").")\n";
@@ -453,8 +587,8 @@ class SimpleXLSXGen {
fwrite($fh, $zdata);
// Append it to central dir
- $e['external_attributes'] = (substr($cfilename, -1) === '/'&&!$zdata)?16:32; // Directory or file name
- $e['comments'] = '';
+ $e['external_attributes'] = (substr($cfilename, -1) === '/' && !$zdata) ? 16 : 32; // Directory or file name
+ $e['comments'] = '';
$cdrec .= $dirSignature;
$cdrec .= "\x0\x0"; // version made by
@@ -466,9 +600,9 @@ class SimpleXLSXGen {
$cdrec .= pack('V', $e['crc_32']); // crc32
$cdrec .= pack('V', $e['comsize']); // compressed filesize
$cdrec .= pack('V', $e['uncsize']); // uncompressed filesize
- $cdrec .= pack('v', mb_strlen($cfilename,'8bit')); // file name length
+ $cdrec .= pack('v', mb_strlen($cfilename, '8bit')); // file name length
$cdrec .= pack('v', 0); // extra field length
- $cdrec .= pack('v', mb_strlen($e['comments'],'8bit')); // file comment length
+ $cdrec .= pack('v', mb_strlen($e['comments'], '8bit')); // file comment length
$cdrec .= pack('v', 0); // disk number start
$cdrec .= pack('v', 0); // internal file attributes
$cdrec .= pack('V', $e['external_attributes']); // internal file attributes
@@ -477,177 +611,221 @@ class SimpleXLSXGen {
$cdrec .= $e['comments'];
}
- protected function _sheetToXML($idx, $template) {
+ protected function _sheetToXML($idx, $template)
+ {
// locale floats fr_FR 1.234,56 -> 1234.56
$_loc = setlocale(LC_NUMERIC, 0);
- setlocale(LC_NUMERIC,'C');
+ setlocale(LC_NUMERIC, 'C');
$COLS = [];
$ROWS = [];
- if ( count($this->sheets[$idx]['rows']) ) {
+ if (count($this->sheets[$idx]['rows'])) {
$COLS[] = '';
$CUR_ROW = 0;
$COL = [];
- foreach( $this->sheets[$idx]['rows'] as $r ) {
+ foreach ($this->sheets[$idx]['rows'] as $r) {
$CUR_ROW++;
$row = '';
$CUR_COL = 0;
$RH = 0; // row height
- foreach( $r as $v ) {
+ foreach ($r as $v) {
$CUR_COL++;
- if ( !isset($COL[ $CUR_COL ])) {
- $COL[ $CUR_COL ] = 0;
+ if (!isset($COL[$CUR_COL])) {
+ $COL[$CUR_COL] = 0;
}
$cname = $this->num2name($CUR_COL) . $CUR_ROW;
- if ( $v === null || $v === '' ) {
- $row .= '';
+ if ($v === null || $v === '') {
+ $row .= '';
continue;
}
- $ct = $cv = null;
- $N = $A = $F = $FL = $C = $B = 0;
+ $ct = $cv = $cf = null;
+ $N = $A = $F = $FL = $C = $BG = 0;
+ $BR = '';
- if ( is_string($v) ) {
+ if (is_string($v)) {
- if ( $v[0] === "\0" ) { // RAW value as string
- $v = substr($v,1);
- $vl = mb_strlen( $v );
+ if ($v{0} === "\0") { // RAW value as string
+ $v = substr($v, 1);
+ $vl = mb_strlen($v);
} else {
- if ( strpos( $v, '<' ) !== false ) { // tags?
- if ( strpos( $v, '' ) !== false ) {
+ if (strpos($v, '<') !== false) { // tags?
+ if (strpos($v, '') !== false) {
$F += self::F_BOLD;
}
- if ( strpos( $v, '' ) !== false ) {
+ if (strpos($v, '') !== false) {
$F += self::F_ITALIC;
}
- if ( strpos( $v, '' ) !== false ) {
+ if (strpos($v, '') !== false) {
$F += self::F_UNDERLINE;
}
- if ( strpos( $v, '' ) !== false ) {
+ if (strpos($v, '') !== false) {
$F += self::F_STRIKE;
}
- if ( preg_match('/