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 { // 01001200 // Простой шаблонБудем делать генератор } - 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 . '">' + . '' + . ''; + } 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('/]+)>/', $v, $m ) ) { + if (preg_match('/]+)>/', $v, $m)) { - if ( preg_match('/ color="([^"]+)"/', $m[1], $m2) ) { + if (preg_match('/ color="([^"]+)"/', $m[1], $m2)) { $F += self::F_COLOR; - $C = strlen($m2[1]) === 8 ? $m2[1] : ('FF' . ltrim($m2[1],'#')); + $c = ltrim($m2[1], '#'); + $C = strlen($c) === 8 ? $c : ('FF' . $c); } - if ( preg_match('/ bgcolor="([^"]+)"/', $m[1], $m2) ) { + if (preg_match('/ bgcolor="([^"]+)"/', $m[1], $m2)) { $FL += self::FL_COLOR; - $B = strlen($m2[1]) === 8 ? $m2[1] : ('FF' . ltrim($m2[1],'#')); + $c = ltrim($m2[1], '#'); + $BG = strlen($c) === 8 ? $c : ('FF' . $c); } - if ( preg_match('/ height="([^"]+)"/', $m[1], $m2) ) { + if (preg_match('/ height="([^"]+)"/', $m[1], $m2)) { $RH = $m2[1]; } + if (preg_match('/ nf="([^"]+)"/', $m[1], $m2)) { + $c = htmlspecialchars_decode($m2[1], ENT_QUOTES); + $N = $this->getNumFmtId($c); + } + if (preg_match('/ border="([^"]+)"/', $m[1], $m2)) { + $b = htmlspecialchars_decode($m2[1], ENT_QUOTES); + if ($b && $b !== 'none') { + $BR = $b; + } + } } - if ( strpos( $v, '' ) !== false ) { + if (strpos($v, '') !== false) { $A += self::A_LEFT; } - if ( strpos( $v, '
' ) !== false ) { + if (strpos($v, '
') !== false) { $A += self::A_CENTER; } - if ( strpos( $v, '' ) !== false ) { + if (strpos($v, '') !== false) { $A += self::A_RIGHT; } - if ( strpos( $v, '' ) !== false ) { + if (strpos($v, '') !== false) { $A += self::A_TOP; } - if ( strpos( $v, '' ) !== false ) { + if (strpos($v, '') !== false) { $A += self::A_MIDDLE; } - if ( strpos( $v, '' ) !== false ) { + if (strpos($v, '') !== false) { $A += self::A_BOTTOM; } - if ( strpos( $v, '' ) !== false ) { + if (strpos($v, '') !== false) { $A += self::A_WRAPTEXT; } - if ( preg_match( '/(.*?)<\/a>/i', $v, $m ) ) { - $h = explode( '#', $m[1] ); - $this->sheets[ $idx ]['hyperlinks'][] = ['ID' => 'rId' . ( count( $this->sheets[ $idx ]['hyperlinks'] ) + 1 ), 'R' => $cname, 'H' => $h[0], 'L' => isset( $h[1] ) ? $h[1] : '']; + if (preg_match('/(.*?)<\/a>/i', $v, $m)) { + $h = explode('#', $m[1]); + $this->extLinkId++; + $this->sheets[$idx]['hyperlinks'][] = ['ID' => 'rId' . $this->extLinkId, 'R' => $cname, 'H' => $h[0], 'L' => isset($h[1]) ? $h[1] : '']; $F += self::F_HYPERLINK; // Hyperlink } - if ( preg_match( '/(.*?)<\/a>/i', $v, $m ) ) { - $this->sheets[ $idx ]['hyperlinks'][] = ['ID' => 'rId' . ( count( $this->sheets[ $idx ]['hyperlinks'] ) + 1 ), 'R' => $cname, 'H' => $m[1], 'L' => '']; + if (preg_match('/(.*?)<\/a>/i', $v, $m)) { + $this->extLinkId++; + $this->sheets[$idx]['hyperlinks'][] = ['ID' => 'rId' . $this->extLinkId, 'R' => $cname, 'H' => $m[1], 'L' => '']; $F += self::F_HYPERLINK; // mailto hyperlink } - $v = strip_tags( $v ); + if (preg_match('/(.*?)<\/a>/i', $v, $m)) { + $this->sheets[$idx]['hyperlinks'][] = ['ID' => null, 'R' => $cname, 'H' => null, 'L' => $m[1]]; + $F += self::F_HYPERLINK; // internal hyperlink + } + if (preg_match('/]*)>/', $v, $m)) { + $cf = strip_tags($v); + $v = 'formula'; + if (preg_match('/ v="([^"]+)"/', $m[1], $m2)) { + $v = $m2[1]; + } + } else { + $v = strip_tags($v); + } } // tags - $vl = mb_strlen( $v ); - if ( $v === '0' || preg_match( '/^[-+]?[1-9]\d{0,14}$/', $v ) ) { // Integer as General - $cv = ltrim( $v, '+' ); - if ( $vl > 10 ) { + $vl = mb_strlen($v); + if ($N) { + $cv = ltrim($v, '+'); + } elseif ($v === '0' || preg_match('/^[-+]?[1-9]\d{0,14}$/', $v)) { // Integer as General + $cv = ltrim($v, '+'); + if ($vl > 10) { $N = self::N_INT; // [1] 0 } - } elseif ( preg_match( '/^[-+]?(0|[1-9]\d*)\.(\d+)$/', $v, $m ) ) { - $cv = ltrim( $v, '+' ); - if ( strlen( $m[2] ) < 3 ) { + } elseif (preg_match('/^[-+]?(0|[1-9]\d*)\.(\d+)$/', $v, $m)) { + $cv = ltrim($v, '+'); + if (strlen($m[2]) < 3) { $N = self::N_DEC; } - } elseif ( preg_match( '/^([-+]?\d+)%$/', $v, $m ) ) { - $cv = round( $m[1] / 100, 2 ); + } elseif (preg_match('/^(\$)?[-+]?[0-9\.]+( ₽| €)?$/u', $v, $m)) { // currency? + if ($m[1] === '$') { + $N = self::N_DOLLAR; + } elseif ($m[2] === ' ₽') { + $N = self::N_RUB; + } elseif ($m[2] === ' €') { + $N = self::N_EURO; + } + $cv = trim($v, ' +$₽€'); + } elseif (preg_match('/^([-+]?\d+)%$/', $v, $m)) { + $cv = round($m[1] / 100, 2); $N = self::N_PERCENT_INT; // [9] 0% - } elseif ( preg_match( '/^([-+]?\d+\.\d+)%$/', $v, $m ) ) { - $cv = round( $m[1] / 100, 4 ); + } elseif (preg_match('/^([-+]?\d+\.\d+)%$/', $v, $m)) { + $cv = round($m[1] / 100, 4); $N = self::N_PRECENT_DEC; // [10] 0.00% - } elseif ( preg_match( '/^(\d\d\d\d)-(\d\d)-(\d\d)$/', $v, $m ) ) { - $cv = $this->date2excel( $m[1], $m[2], $m[3] ); + } elseif (preg_match('/^(\d\d\d\d)-(\d\d)-(\d\d)$/', $v, $m)) { + $cv = $this->date2excel($m[1], $m[2], $m[3]); $N = self::N_DATE; // [14] mm-dd-yy - } elseif ( preg_match( '/^(\d\d)\/(\d\d)\/(\d\d\d\d)$/', $v, $m ) ) { - $cv = $this->date2excel( $m[3], $m[2], $m[1] ); + } elseif (preg_match('/^(\d\d)\/(\d\d)\/(\d\d\d\d)$/', $v, $m)) { + $cv = $this->date2excel($m[3], $m[2], $m[1]); $N = self::N_DATE; // [14] mm-dd-yy - } elseif ( preg_match( '/^(\d\d):(\d\d):(\d\d)$/', $v, $m ) ) { - $cv = $this->date2excel( 0, 0, 0, $m[1], $m[2], $m[3] ); + } elseif (preg_match('/^(\d\d):(\d\d):(\d\d)$/', $v, $m)) { + $cv = $this->date2excel(0, 0, 0, $m[1], $m[2], $m[3]); $N = self::N_TIME; // time - } elseif ( preg_match( '/^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/', $v, $m ) ) { - $cv = $this->date2excel( $m[1], $m[2], $m[3], $m[4], $m[5], $m[6] ); - $N = ((int) $m[1] === 0) ? self::N_TIME : self::N_DATETIME; // [22] m/d/yy h:mm - } elseif ( preg_match( '/^(\d\d)\/(\d\d)\/(\d\d\d\d) (\d\d):(\d\d):(\d\d)$/', $v, $m ) ) { - $cv = $this->date2excel( $m[3], $m[2], $m[1], $m[4], $m[5], $m[6] ); + } elseif (preg_match('/^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/', $v, $m)) { + $cv = $this->date2excel($m[1], $m[2], $m[3], $m[4], $m[5], $m[6]); + $N = ((int)$m[1] === 0) ? self::N_TIME : self::N_DATETIME; // [22] m/d/yy h:mm + } elseif (preg_match('/^(\d\d)\/(\d\d)\/(\d\d\d\d) (\d\d):(\d\d):(\d\d)$/', $v, $m)) { + $cv = $this->date2excel($m[3], $m[2], $m[1], $m[4], $m[5], $m[6]); $N = self::N_DATETIME; // [22] m/d/yy h:mm - } elseif ( preg_match( '/^[0-9+-.]+$/', $v ) ) { // Long ? + } elseif (preg_match('/^[0-9+-.]+$/', $v)) { // Long ? $A += ($A & (self::A_LEFT | self::A_CENTER)) ? 0 : self::A_RIGHT; - } elseif ( preg_match( '/^https?:\/\/\S+$/i', $v ) ) { - $h = explode( '#', $v ); - $this->sheets[ $idx ]['hyperlinks'][] = ['ID' => 'rId' . ( count( $this->sheets[ $idx ]['hyperlinks'] ) + 1 ), 'R' => $cname, 'H' => $h[0], 'L' => isset( $h[1] ) ? $h[1] : '']; + } elseif (preg_match('/^https?:\/\/\S+$/i', $v)) { + $h = explode('#', $v); + $this->extLinkId++; + $this->sheets[$idx]['hyperlinks'][] = ['ID' => 'rId' . $this->extLinkId, 'R' => $cname, 'H' => $h[0], 'L' => isset($h[1]) ? $h[1] : '']; $F += self::F_HYPERLINK; // Hyperlink - } elseif ( preg_match( "/^[a-zA-Z0-9_\.\-]+@([a-zA-Z0-9][a-zA-Z0-9\-]*\.)+[a-zA-Z]{2,}$/", $v ) ) { - $this->sheets[ $idx ]['hyperlinks'][] = ['ID' => 'rId' . ( count( $this->sheets[ $idx ]['hyperlinks'] ) + 1 ), 'R' => $cname, 'H' => 'mailto:' . $v, 'L' => '']; + } elseif (preg_match("/^[a-zA-Z0-9_\.\-]+@([a-zA-Z0-9][a-zA-Z0-9\-]*\.)+[a-zA-Z]{2,}$/", $v)) { + $this->extLinkId++; + $this->sheets[$idx]['hyperlinks'][] = ['ID' => 'rId' . $this->extLinkId, 'R' => $cname, 'H' => 'mailto:' . $v, 'L' => '']; $F += self::F_HYPERLINK; // Hyperlink } - if ( ($N === self::N_DATE || $N === self::N_DATETIME) && $cv < 0 ) { + if (($N === self::N_DATE || $N === self::N_DATETIME) && $cv < 0) { $cv = null; $N = 0; } } - if ( $cv === null ) { + if ($cv === null) { - $v = $this->esc( $v ); + $v = $this->esc($v); - if ( mb_strlen( $v ) > 160 ) { + if ($cf) { + $ct = 'str'; + $cv = $v; + } elseif (mb_strlen($v) > 160) { $ct = 'inlineStr'; $cv = $v; } else { $ct = 's'; // shared string $cv = false; $skey = '~' . $v; - if ( isset( $this->SI_KEYS[ $skey ] ) ) { - $cv = $this->SI_KEYS[ $skey ]; + if (isset($this->SI_KEYS[$skey])) { + $cv = $this->SI_KEYS[$skey]; } - if ( $cv === false ) { + if ($cv === false) { $this->SI[] = $v; - $cv = count( $this->SI ) - 1; - $this->SI_KEYS[ $skey ] = $cv; + $cv = count($this->SI) - 1; + $this->SI_KEYS[$skey] = $cv; } } } - } elseif ( is_int( $v ) ) { - $vl = mb_strlen( (string) $v ); + } elseif (is_int($v)) { + $vl = mb_strlen((string)$v); $cv = $v; - } elseif ( is_float( $v ) ) { - $vl = mb_strlen( (string) $v ); + } elseif (is_float($v)) { + $vl = mb_strlen((string)$v); $cv = $v; - } elseif ( $v instanceof \DateTime ) { + } elseif ($v instanceof \DateTime) { $vl = 16; $cv = $this->date2excel($v->format('Y'), $v->format('m'), $v->format('d'), $v->format('H'), $v->format('i'), $v->format('s')); $N = self::N_DATETIME; // [22] m/d/yy h:mm @@ -655,43 +833,44 @@ class SimpleXLSXGen { continue; } - $COL[ $CUR_COL ] = max( $vl, $COL[ $CUR_COL ] ); + $COL[$CUR_COL] = max($vl, $COL[$CUR_COL]); $cs = 0; - if ( $N + $A + $F + $FL > 0 ) { + if (($N + $A + $F + $FL > 0) || $BR !== '') { - if ( $FL === self::FL_COLOR ) { + if ($FL === self::FL_COLOR) { $FL += self::FL_SOLID; } - if ( ($F & self::F_HYPERLINK) && !($F & self::F_COLOR)) { + if (($F & self::F_HYPERLINK) && !($F & self::F_COLOR)) { $F += self::F_COLOR; $C = 'FF0563C1'; } - $XF_KEY = $N . '-' . $A . '-' . $F. '-'. $FL . '-' . $C . '-' . $B; + $XF_KEY = $N . '-' . $A . '-' . $F . '-' . $FL . '-' . $C . '-' . $BG . '-' . $BR; // echo $cname .'='.$XF_KEY.PHP_EOL; - if ( isset( $this->XF_KEYS[ $XF_KEY ] ) ) { - $cs = $this->XF_KEYS[ $XF_KEY ]; + if (isset($this->XF_KEYS[$XF_KEY])) { + $cs = $this->XF_KEYS[$XF_KEY]; } - if ( $cs === 0 ) { - $cs = count( $this->XF ); - $this->XF_KEYS[ $XF_KEY ] = $cs; - $this->XF[] = [$N, $A, $F, $FL, $C, $B]; + if ($cs === 0) { + $cs = count($this->XF); + $this->XF_KEYS[$XF_KEY] = $cs; + $this->XF[] = [$N, $A, $F, $FL, $C, $BG, $BR]; } } $row .= '' - . ($ct === 'inlineStr' ? '' . $cv . '' : '' . $cv . '') . "\r\n"; + . ($cf ? '' . $cf . '' : '') + . ($ct === 'inlineStr' ? '' . $cv . '' : '' . $cv . '') . "\r\n"; } - $ROWS[] = ''.$row . ""; + $ROWS[] = '' . $row . ""; } - foreach ( $COL as $k => $max ) { - $w = isset($this->sheets[$idx]['colwidth'][$k]) ? $this->sheets[$idx]['colwidth'][$k] : min( $max+1, 60); - $COLS[] = ''; + foreach ($COL as $k => $max) { + $w = isset($this->sheets[$idx]['colwidth'][$k]) ? $this->sheets[$idx]['colwidth'][$k] : min($max + 1, 60); + $COLS[] = ''; } $COLS[] = ''; - $REF = 'A1:'.$this->num2name(count($COL)) . $CUR_ROW; + $REF = 'A1:' . $this->num2name(count($COL)) . $CUR_ROW; } else { $ROWS[] = '0'; $REF = 'A1:A1'; @@ -699,24 +878,24 @@ class SimpleXLSXGen { $AUTOFILTER = ''; if ($this->sheets[$idx]['autofilter']) { - $AUTOFILTER = ''; + $AUTOFILTER = ''; } $MERGECELLS = []; - if ( count($this->sheets[$idx]['mergecells']) ) { + if (count($this->sheets[$idx]['mergecells'])) { $MERGECELLS[] = ''; - $MERGECELLS[] = ''; - foreach( $this->sheets[$idx]['mergecells'] as $m ) { - $MERGECELLS[] = ''; + $MERGECELLS[] = ''; + foreach ($this->sheets[$idx]['mergecells'] as $m) { + $MERGECELLS[] = ''; } $MERGECELLS[] = ''; } $HYPERLINKS = []; - if ( count( $this->sheets[$idx]['hyperlinks']) ) { + if (count($this->sheets[$idx]['hyperlinks'])) { $HYPERLINKS[] = ''; - foreach ( $this->sheets[$idx]['hyperlinks'] as $h ) { - $HYPERLINKS[] = ''; + foreach ($this->sheets[$idx]['hyperlinks'] as $h) { + $HYPERLINKS[] = ''; } $HYPERLINKS[] = ''; } @@ -724,38 +903,42 @@ class SimpleXLSXGen { //restore locale setlocale(LC_NUMERIC, $_loc); - return str_replace(['{REF}','{COLS}','{ROWS}','{AUTOFILTER}','{MERGECELLS}','{HYPERLINKS}'], - [ $REF, + return str_replace(['{REF}', '{COLS}', '{ROWS}', '{AUTOFILTER}', '{MERGECELLS}', '{HYPERLINKS}'], + [$REF, implode("\r\n", $COLS), - implode("\r\n",$ROWS), + implode("\r\n", $ROWS), $AUTOFILTER, implode("\r\n", $MERGECELLS), implode("\r\n", $HYPERLINKS) ], - $template ); + $template); } - public function num2name($num) { + public function num2name($num) + { $numeric = ($num - 1) % 26; - $letter = chr( 65 + $numeric ); - $num2 = (int) ( ($num-1) / 26 ); - if ( $num2 > 0 ) { - return $this->num2name( $num2 ) . $letter; + $letter = chr(65 + $numeric); + $num2 = (int)(($num - 1) / 26); + if ($num2 > 0) { + return $this->num2name($num2) . $letter; } return $letter; } - public function date2excel($year, $month, $day, $hours=0, $minutes=0, $seconds=0) { + public function date2excel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0) + { $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400; - if ( (int) $year === 0 ) { + if ((int)$year === 0) { return $excelTime; } // self::CALENDAR_WINDOWS_1900 $excel1900isLeapYear = True; - if (($year === 1900) && ($month <= 2)) { $excel1900isLeapYear = False; } + if (($year === 1900) && ($month <= 2)) { + $excel1900isLeapYear = False; + } $myExcelBaseDate = 2415020; // Julian base date Adjustment @@ -765,38 +948,68 @@ class SimpleXLSXGen { $month += 9; --$year; } - $century = substr($year,0,2); - $decade = substr($year,2,2); + $century = substr($year, 0, 2); + $decade = substr($year, 2, 2); // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0) $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myExcelBaseDate + $excel1900isLeapYear; - return (float) $excelDate + $excelTime; + return (float)$excelDate + $excelTime; } - public function setDefaultFont( $name ) { + + public function setDefaultFont($name) + { $this->defaultFont = $name; return $this; } - public function setDefaultFontSize( $size ) { + + public function setDefaultFontSize($size) + { $this->defaultFontSize = $size; return $this; } - public function autoFilter( $range ) { + public function autoFilter($range) + { $this->sheets[$this->curSheet]['autofilter'] = $range; return $this; } - public function mergeCells( $range ) { + public function mergeCells($range) + { $this->sheets[$this->curSheet]['mergecells'][] = $range; return $this; } - public function setColWidth($col, $width) { + + public function setColWidth($col, $width) + { $this->sheets[$this->curSheet]['colwidth'][$col] = $width; return $this; } - public function esc( $str ) { + + public function esc($str) + { // XML UTF-8: #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] // but we use fast version - return str_replace( ['&', '<', '>', "\x00","\x03","\x0B"], ['&', '<', '>', '', '', ''], $str ); + return str_replace(['&', '<', '>', "\x00", "\x03", "\x0B"], ['&', '<', '>', '', '', ''], $str); } + + public function getNumFmtId($code) + { + if (isset($this->NF[$code])) { // id? + return (int)$code; + } + if (isset($this->NF_KEYS[$code])) { + return $this->NF_KEYS[$code]; + } + $id = 197 + count($this->NF); // custom + $this->NF[$id] = $code; + $this->NF_KEYS[$code] = $id; + return $id; + } + + public static function raw($value) + { + return "\0" . (string)$value; + } + } diff --git a/styles.png b/styles.png index 26b8ff4..0ad95da 100644 Binary files a/styles.png and b/styles.png differ