1
0
mirror of https://github.com/shuchkin/simplexlsxgen.git synced 2023-08-10 21:12:59 +03:00
This commit is contained in:
Sergey Shuchkin 2022-12-14 21:30:23 +06:00
parent 75252a4a0c
commit c1434378bc
5 changed files with 525 additions and 295 deletions

View File

@ -1,4 +1,12 @@
# Changelog # Changelog
## 1.3.10 (2022-12-14)
* added borders ```<style border="medium">Black Border</style>``` see colored [examples](https://github.com/shuchkin/simplexlsxgen#formatting)
* added formulas ```<f v="100">SUM(B1:B10)</f>``` see [examples](https://github.com/shuchkin/simplexlsxgen#data-types)
* added internal links ```<a href="sheet2!A1">Go to page 2</a>```
* added custom number formats ```<style nf="&quot;£&quot;#,##0.00">500</style>```
* added 3 currencies ```$data = [ ['$100.23', '2000.00 €', '1200.30 ₽'] ];```
## 1.2.16 (2022-08-12) ## 1.2.16 (2022-08-12)
* added `autoFilter( $range )` * added `autoFilter( $range )`
```php ```php

View File

@ -39,16 +39,22 @@ $data = [
['Integer', 123], ['Integer', 123],
['Float', 12.35], ['Float', 12.35],
['Percent', '12%'], ['Percent', '12%'],
['Currency $', '$500.67'],
['Currency €', '200 €'],
['Currency ₽', '1200.30 ₽'],
['Currency (other)', '<style nf="&quot;£&quot;#,##0.00">500</style>'],
['Datetime', '2020-05-20 02:38:00'], ['Datetime', '2020-05-20 02:38:00'],
['Date', '2020-05-20'], ['Date', '2020-05-20'],
['Time', '02:38:00'], ['Time', '02:38:00'],
['Datetime PHP', new DateTime('2021-02-06 21:07:00')], ['Datetime PHP', new DateTime('2021-02-06 21:07:00')],
['String', 'Long UTF-8 String in autoresized column'], ['String', 'Long UTF-8 String in autoresized column'],
['Formula', '<f v="135.35">SUM(B1:B2)</f>'],
['Hyperlink', 'https://github.com/shuchkin/simplexlsxgen'], ['Hyperlink', 'https://github.com/shuchkin/simplexlsxgen'],
['Hyperlink + Anchor', '<a href="https://github.com/shuchkin/simplexlsxgen">SimpleXLSXGen</a>'], ['Hyperlink + Anchor', '<a href="https://github.com/shuchkin/simplexlsxgen">SimpleXLSXGen</a>'],
['Internal link', '<a href="sheet2!A1">Go to second page</a>'],
['RAW string', "\0" . '2020-10-04 16:02:00'] ['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) ![XLSX screenshot](datatypes.png)
@ -66,6 +72,9 @@ $data = [
['Green', '<style color="#00FF00">12345.67</style>'], ['Green', '<style color="#00FF00">12345.67</style>'],
['Bold Red Text', '<b><style color="#FF0000">12345.67</style></b>'], ['Bold Red Text', '<b><style color="#FF0000">12345.67</style></b>'],
['Blue Text and Yellow Fill', '<style bgcolor="#FFFF00" color="#0000FF">12345.67</style>'], ['Blue Text and Yellow Fill', '<style bgcolor="#FFFF00" color="#0000FF">12345.67</style>'],
['Border color', '<style border="#000000">Black Thin Border</style>'],
['<top>Border style</top>','<style border="medium"><wraptext>none, thin, medium, dashed, dotted, thick, double, hair, mediumDashed, dashDot,mediumDashDot, dashDotDot, mediumDashDotDot, slantDashDot</wraptext></style>'],
['Border sides', '<style border="none dotted#0000FF medium#FF0000 double">Top No + Right Dotted + Bottom medium + Left double</style>'],
['Left', '<left>12345.67</left>'], ['Left', '<left>12345.67</left>'],
['Center', '<center>12345.67</center>'], ['Center', '<center>12345.67</center>'],
['Right', '<right>Right Text</right>'], ['Right', '<right>Right Text</right>'],
@ -80,7 +89,7 @@ $data = [
SimpleXLSXGen::fromArray($data) SimpleXLSXGen::fromArray($data)
->setDefaultFont('Courier New') ->setDefaultFont('Courier New')
->setDefaultFontSize(14) ->setDefaultFontSize(14)
->setColWidth(1, 35) // 1 - num column, 35 - size in chars ->setColWidth(1, 35)
->mergeCells('A20:B20') ->mergeCells('A20:B20')
->saveAs('styles_and_tags.xlsx'); ->saveAs('styles_and_tags.xlsx');
``` ```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -11,16 +11,22 @@ namespace Shuchkin;
* Export data to MS Excel. PHP XLSX generator * Export data to MS Excel. PHP XLSX generator
* Author: sergey.shuchkin@gmail.com * Author: sergey.shuchkin@gmail.com
*/ */
class SimpleXLSXGen
class SimpleXLSXGen { {
public $curSheet; public $curSheet;
protected $defaultFont; protected $defaultFont;
protected $defaultFontSize; protected $defaultFontSize;
protected $sheets; protected $sheets;
protected $template; protected $template;
protected $XF, $XF_KEYS; // cellXfs protected $NF; // numFmts
protected $SI, $SI_KEYS; // shared strings 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_NORMAL = 0; // General
const N_INT = 1; // 0 const N_INT = 1; // 0
const N_DEC = 2; // 0.00 const N_DEC = 2; // 0.00
@ -28,6 +34,9 @@ class SimpleXLSXGen {
const N_PRECENT_DEC = 10; // 0.00% const N_PRECENT_DEC = 10; // 0.00%
const N_DATE = 14; // mm-dd-yy const N_DATE = 14; // mm-dd-yy
const N_TIME = 20; // h:mm 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 N_DATETIME = 22; // m/d/yy h:mm
const F_NORMAL = 0; const F_NORMAL = 0;
const F_HYPERLINK = 1; const F_HYPERLINK = 1;
@ -51,19 +60,64 @@ class SimpleXLSXGen {
const A_MIDDLE = 16; const A_MIDDLE = 16;
const A_BOTTOM = 32; const A_BOTTOM = 32;
const A_WRAPTEXT = 64; 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->curSheet = -1;
$this->defaultFont = 'Calibri'; $this->defaultFont = 'Calibri';
$this->sheets = [['name' => 'Sheet1', 'rows' => [], 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => '']]; $this->sheets = [['name' => 'Sheet1', 'rows' => [], 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => '']];
$this->extLinkId = 0;
$this->SI = []; // sharedStrings index $this->SI = []; // sharedStrings index
$this->SI_KEYS = []; // & keys $this->SI_KEYS = []; // & keys
$this->XF = [ // styles
[self::N_NORMAL, self::A_DEFAULT, self::F_NORMAL, self::FL_NONE, 0, 0], // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_numFmts_topic_ID0E6KK6.html
[self::N_NORMAL, self::A_DEFAULT, self::F_NORMAL, self::FL_GRAY_125, 0, 0], // hack $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->NF_KEYS = array_flip($this->NF);
$this->XF_KEYS['0-0-0-16-0-0'] = 1;
$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 = [ $this->template = [
'_rels/.rels' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?> '_rels/.rels' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
@ -97,9 +151,10 @@ class SimpleXLSXGen {
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="{CNT}" uniqueCount="{CNT}">{STRINGS}</sst>', <sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="{CNT}" uniqueCount="{CNT}">{STRINGS}</sst>',
'xl/styles.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 'xl/styles.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"> <styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
{NUMFMTS}
{FONTS} {FONTS}
{FILLS} {FILLS}
<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders> {BORDERS}
<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" /></cellStyleXfs> <cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" /></cellStyleXfs>
{XF} {XF}
<cellStyles count="1"> <cellStyles count="1">
@ -128,11 +183,14 @@ 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> // <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> // <si><t>Простой шаблон</t></si><si><t>Будем делать генератор</t></si>
} }
public static function fromArray( array $rows, $sheetName = null ) {
public static function fromArray(array $rows, $sheetName = null)
{
return (new static())->addSheet($rows, $sheetName); return (new static())->addSheet($rows, $sheetName);
} }
public function addSheet( array $rows, $name = null ) { public function addSheet(array $rows, $name = null)
{
$this->curSheet++; $this->curSheet++;
if ($name === null) { // autogenerated sheet names if ($name === null) { // autogenerated sheet names
@ -167,7 +225,8 @@ class SimpleXLSXGen {
return $this; return $this;
} }
public function __toString() { public function __toString()
{
$fh = fopen('php://memory', 'wb'); $fh = fopen('php://memory', 'wb');
if (!$fh) { if (!$fh) {
return ''; return '';
@ -183,7 +242,8 @@ class SimpleXLSXGen {
return (string)fread($fh, $size); return (string)fread($fh, $size);
} }
public function saveAs( $filename ) { public function saveAs($filename)
{
$fh = fopen($filename, 'wb'); $fh = fopen($filename, 'wb');
if (!$fh) { if (!$fh) {
return false; return false;
@ -197,11 +257,13 @@ class SimpleXLSXGen {
return true; return true;
} }
public function download() { public function download()
{
return $this->downloadAs(gmdate('YmdHi') . '.xlsx'); return $this->downloadAs(gmdate('YmdHi') . '.xlsx');
} }
public function downloadAs( $filename ) { public function downloadAs($filename)
{
$fh = fopen('php://memory', 'wb'); $fh = fopen('php://memory', 'wb');
if (!$fh) { if (!$fh) {
return false; return false;
@ -229,7 +291,8 @@ class SimpleXLSXGen {
return true; return true;
} }
protected function _write( $fh ) { protected function _write($fh)
{
$dirSignatureE = "\x50\x4b\x05\x06"; // end of central dir signature $dirSignatureE = "\x50\x4b\x05\x06"; // end of central dir signature
@ -262,8 +325,7 @@ class SimpleXLSXGen {
$template = str_replace('{SHEETS}', $s, $template); $template = str_replace('{SHEETS}', $s, $template);
$this->_writeEntry($fh, $cdrec, $cfilename, $template); $this->_writeEntry($fh, $cdrec, $cfilename, $template);
$entries++; $entries++;
} } elseif ($cfilename === 'docProps/core.xml') {
elseif ( $cfilename === 'docProps/core.xml' ) {
$template = str_replace('{DATE}', gmdate('Y-m-d\TH:i:s\Z'), $template); $template = str_replace('{DATE}', gmdate('Y-m-d\TH:i:s\Z'), $template);
$this->_writeEntry($fh, $cdrec, $cfilename, $template); $this->_writeEntry($fh, $cdrec, $cfilename, $template);
$entries++; $entries++;
@ -286,12 +348,14 @@ class SimpleXLSXGen {
$xml = null; $xml = null;
} elseif ($cfilename === 'xl/worksheets/_rels/sheet1.xml.rels') { } elseif ($cfilename === 'xl/worksheets/_rels/sheet1.xml.rels') {
foreach ($this->sheets as $k => $v) { foreach ($this->sheets as $k => $v) {
if ( count($v['hyperlinks'])) { if ($this->extLinkId) {
$RH = []; $RH = [];
$filename = 'xl/worksheets/_rels/sheet' . ($k + 1) . '.xml.rels'; $filename = 'xl/worksheets/_rels/sheet' . ($k + 1) . '.xml.rels';
foreach ($v['hyperlinks'] as $h) { foreach ($v['hyperlinks'] as $h) {
if ($h['ID']) {
$RH[] = '<Relationship Id="' . $h['ID'] . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="' . $this->esc($h['H']) . '" TargetMode="External"/>'; $RH[] = '<Relationship Id="' . $h['ID'] . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="' . $this->esc($h['H']) . '" TargetMode="External"/>';
} }
}
$xml = str_replace('{HYPERLINKS}', implode("\r\n", $RH), $template); $xml = str_replace('{HYPERLINKS}', implode("\r\n", $RH), $template);
$this->_writeEntry($fh, $cdrec, $filename, $xml); $this->_writeEntry($fh, $cdrec, $filename, $xml);
$entries++; $entries++;
@ -304,7 +368,7 @@ class SimpleXLSXGen {
foreach ($this->sheets as $k => $v) { foreach ($this->sheets as $k => $v) {
$TYPES[] = '<Override PartName="/xl/worksheets/sheet' . ($k + 1) . $TYPES[] = '<Override PartName="/xl/worksheets/sheet' . ($k + 1) .
'.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'; '.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>';
if (count( $v['hyperlinks'])) { if ($this->extLinkId) {
$TYPES[] = '<Override PartName="/xl/worksheets/_rels/sheet' . ($k + 1) . '.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'; $TYPES[] = '<Override PartName="/xl/worksheets/_rels/sheet' . ($k + 1) . '.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
} }
} }
@ -312,10 +376,16 @@ class SimpleXLSXGen {
$this->_writeEntry($fh, $cdrec, $cfilename, $template); $this->_writeEntry($fh, $cdrec, $cfilename, $template);
$entries++; $entries++;
} elseif ($cfilename === 'xl/styles.xml') { } elseif ($cfilename === 'xl/styles.xml') {
$XF = $FONTS = $F_KEYS = $FILLS = $FL_KEYS = []; $NF = $XF = $FONTS = $F_KEYS = $FILLS = $FL_KEYS = [];
// print_r( $this->XF ); $BR = ['<border><left/><right/><top/><bottom/><diagonal/></border>'];
$BR_KEYS = [0 => 0];
foreach ($this->NF as $k => $v) {
$NF[] = '<numFmt numFmtId="' . $k . '" formatCode="' . htmlspecialchars($v, ENT_QUOTES) . '"/>';
}
foreach ($this->XF as $xf) { foreach ($this->XF as $xf) {
// 0 - num fmt, 1 - align, 2 - font, 3 - fill, 4 - font color, 5 - bgcolor // 0 - num fmt, 1 - align, 2 - font, 3 - fill, 4 - font color, 5 - bgcolor, 6 - border
// fonts // fonts
$F_KEY = $xf[2] . '-' . $xf[4]; $F_KEY = $xf[2] . '-' . $xf[4];
if (isset($F_KEYS[$F_KEY])) { if (isset($F_KEYS[$F_KEY])) {
@ -358,22 +428,85 @@ class SimpleXLSXGen {
. ($xf[1] & self::A_BOTTOM ? ' vertical="bottom"' : '') . ($xf[1] & self::A_BOTTOM ? ' vertical="bottom"' : '')
. ($xf[1] & self::A_WRAPTEXT ? ' wrapText="1"' : ''); . ($xf[1] & self::A_WRAPTEXT ? ' wrapText="1"' : '');
$XF[] = '<xf numFmtId="'.$xf[0].'" fontId="'.$F_ID.'" fillId="'.$FL_ID.'" borderId="0" xfId="0"' // 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 = '<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 . '">'
. '<color ' . ($c === '' ? 'auto="1"' : 'rgb="' . $c . '"') . '/>'
. '</' . $side . '>';
} else {
$border .= '<' . $side . '/>';
}
}
$border .= '</border>';
$BR[] = $border;
}
}
$XF[] = '<xf numFmtId="' . $xf[0] . '" fontId="' . $F_ID . '" fillId="' . $FL_ID . '" borderId="' . $BR_ID . '" xfId="0"'
. ($xf[0] > 0 ? ' applyNumberFormat="1"' : '') . ($xf[0] > 0 ? ' applyNumberFormat="1"' : '')
. ($F_ID > 0 ? ' applyFont="1"' : '') . ($F_ID > 0 ? ' applyFont="1"' : '')
. ($FL_ID > 0 ? ' applyFill="1"' : '') . ($FL_ID > 0 ? ' applyFill="1"' : '')
. ($BR_ID > 0 ? ' applyBorder="1"' : '')
. ($align ? ' applyAlignment="1"><alignment' . $align . '/></xf>' : '/>'); . ($align ? ' applyAlignment="1"><alignment' . $align . '/></xf>' : '/>');
} }
// wrap collections // wrap collections
array_unshift($NF, '<numFmts count="' . count($NF) . '">');
$NF[] = '</numFmts>';
array_unshift($XF, '<cellXfs count="' . count($XF) . '">'); array_unshift($XF, '<cellXfs count="' . count($XF) . '">');
$XF[] = '</cellXfs>'; $XF[] = '</cellXfs>';
array_unshift($FONTS, '<fonts count="' . count($FONTS) . '">'); array_unshift($FONTS, '<fonts count="' . count($FONTS) . '">');
$FONTS[] = '</fonts>'; $FONTS[] = '</fonts>';
array_unshift($FILLS, '<fills count="' . count($FILLS) . '">'); array_unshift($FILLS, '<fills count="' . count($FILLS) . '">');
$FILLS[] = '</fills>'; $FILLS[] = '</fills>';
array_unshift($BR, '<borders count="' . count($BR) . '">');
$BR[] = '</borders>';
$template = str_replace(['{FONTS}','{XF}','{FILLS}'], [implode("\r\n", $FONTS), implode("\r\n", $XF), implode("\r\n", $FILLS)], $template); $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); $this->_writeEntry($fh, $cdrec, $cfilename, $template);
$entries++; $entries++;
} else { } else {
@ -398,7 +531,8 @@ class SimpleXLSXGen {
return true; 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 $zipSignature = "\x50\x4b\x03\x04"; // local file header signature
$dirSignature = "\x50\x4b\x01\x02"; // central dir header signature $dirSignature = "\x50\x4b\x01\x02"; // central dir header signature
@ -477,7 +611,8 @@ class SimpleXLSXGen {
$cdrec .= $e['comments']; $cdrec .= $e['comments'];
} }
protected function _sheetToXML($idx, $template) { protected function _sheetToXML($idx, $template)
{
// locale floats fr_FR 1.234,56 -> 1234.56 // locale floats fr_FR 1.234,56 -> 1234.56
$_loc = setlocale(LC_NUMERIC, 0); $_loc = setlocale(LC_NUMERIC, 0);
setlocale(LC_NUMERIC, 'C'); setlocale(LC_NUMERIC, 'C');
@ -504,12 +639,13 @@ class SimpleXLSXGen {
continue; continue;
} }
$ct = $cv = null; $ct = $cv = $cf = null;
$N = $A = $F = $FL = $C = $B = 0; $N = $A = $F = $FL = $C = $BG = 0;
$BR = '';
if (is_string($v)) { if (is_string($v)) {
if ( $v[0] === "\0" ) { // RAW value as string if ($v{0} === "\0") { // RAW value as string
$v = substr($v, 1); $v = substr($v, 1);
$vl = mb_strlen($v); $vl = mb_strlen($v);
} else { } else {
@ -531,15 +667,27 @@ class SimpleXLSXGen {
if (preg_match('/ color="([^"]+)"/', $m[1], $m2)) { if (preg_match('/ color="([^"]+)"/', $m[1], $m2)) {
$F += self::F_COLOR; $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; $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]; $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, '<left>') !== false) { if (strpos($v, '<left>') !== false) {
$A += self::A_LEFT; $A += self::A_LEFT;
@ -564,17 +712,33 @@ class SimpleXLSXGen {
} }
if (preg_match('/<a href="(https?:\/\/[^"]+)">(.*?)<\/a>/i', $v, $m)) { if (preg_match('/<a href="(https?:\/\/[^"]+)">(.*?)<\/a>/i', $v, $m)) {
$h = explode('#', $m[1]); $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] : '']; $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 $F += self::F_HYPERLINK; // Hyperlink
} }
if (preg_match('/<a href="(mailto?:[^"]+)">(.*?)<\/a>/i', $v, $m)) { if (preg_match('/<a href="(mailto?:[^"]+)">(.*?)<\/a>/i', $v, $m)) {
$this->sheets[ $idx ]['hyperlinks'][] = ['ID' => 'rId' . ( count( $this->sheets[ $idx ]['hyperlinks'] ) + 1 ), 'R' => $cname, 'H' => $m[1], 'L' => '']; $this->extLinkId++;
$this->sheets[$idx]['hyperlinks'][] = ['ID' => 'rId' . $this->extLinkId, 'R' => $cname, 'H' => $m[1], 'L' => ''];
$F += self::F_HYPERLINK; // mailto hyperlink $F += self::F_HYPERLINK; // mailto hyperlink
} }
if (preg_match('/<a href="([^"]+![^"]+)">(.*?)<\/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('/<f([^>]*)>/', $v, $m)) {
$cf = strip_tags($v);
$v = 'formula';
if (preg_match('/ v="([^"]+)"/', $m[1], $m2)) {
$v = $m2[1];
}
} else {
$v = strip_tags($v); $v = strip_tags($v);
}
} // tags } // tags
$vl = mb_strlen($v); $vl = mb_strlen($v);
if ( $v === '0' || preg_match( '/^[-+]?[1-9]\d{0,14}$/', $v ) ) { // Integer as General if ($N) {
$cv = ltrim($v, '+');
} elseif ($v === '0' || preg_match('/^[-+]?[1-9]\d{0,14}$/', $v)) { // Integer as General
$cv = ltrim($v, '+'); $cv = ltrim($v, '+');
if ($vl > 10) { if ($vl > 10) {
$N = self::N_INT; // [1] 0 $N = self::N_INT; // [1] 0
@ -584,6 +748,15 @@ class SimpleXLSXGen {
if (strlen($m[2]) < 3) { if (strlen($m[2]) < 3) {
$N = self::N_DEC; $N = self::N_DEC;
} }
} 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)) { } elseif (preg_match('/^([-+]?\d+)%$/', $v, $m)) {
$cv = round($m[1] / 100, 2); $cv = round($m[1] / 100, 2);
$N = self::N_PERCENT_INT; // [9] 0% $N = self::N_PERCENT_INT; // [9] 0%
@ -609,10 +782,12 @@ class SimpleXLSXGen {
$A += ($A & (self::A_LEFT | self::A_CENTER)) ? 0 : self::A_RIGHT; $A += ($A & (self::A_LEFT | self::A_CENTER)) ? 0 : self::A_RIGHT;
} elseif (preg_match('/^https?:\/\/\S+$/i', $v)) { } elseif (preg_match('/^https?:\/\/\S+$/i', $v)) {
$h = explode('#', $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] : '']; $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 $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)) { } 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' => '']; $this->extLinkId++;
$this->sheets[$idx]['hyperlinks'][] = ['ID' => 'rId' . $this->extLinkId, 'R' => $cname, 'H' => 'mailto:' . $v, 'L' => ''];
$F += self::F_HYPERLINK; // Hyperlink $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) {
@ -624,7 +799,10 @@ class SimpleXLSXGen {
$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'; $ct = 'inlineStr';
$cv = $v; $cv = $v;
} else { } else {
@ -659,7 +837,7 @@ class SimpleXLSXGen {
$cs = 0; $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; $FL += self::FL_SOLID;
@ -669,7 +847,7 @@ class SimpleXLSXGen {
$C = 'FF0563C1'; $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; // echo $cname .'='.$XF_KEY.PHP_EOL;
if (isset($this->XF_KEYS[$XF_KEY])) { if (isset($this->XF_KEYS[$XF_KEY])) {
$cs = $this->XF_KEYS[$XF_KEY]; $cs = $this->XF_KEYS[$XF_KEY];
@ -677,11 +855,12 @@ class SimpleXLSXGen {
if ($cs === 0) { if ($cs === 0) {
$cs = count($this->XF); $cs = count($this->XF);
$this->XF_KEYS[$XF_KEY] = $cs; $this->XF_KEYS[$XF_KEY] = $cs;
$this->XF[] = [$N, $A, $F, $FL, $C, $B]; $this->XF[] = [$N, $A, $F, $FL, $C, $BG, $BR];
} }
} }
$row .= '<c r="' . $cname . '"' . ($ct ? ' t="' . $ct . '"' : '') . ($cs ? ' s="' . $cs . '"' : '') . '>' $row .= '<c r="' . $cname . '"' . ($ct ? ' t="' . $ct . '"' : '') . ($cs ? ' s="' . $cs . '"' : '') . '>'
. ($cf ? '<f>' . $cf . '</f>' : '')
. ($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 r="' . $CUR_ROW . '"' . ($RH ? ' customHeight="1" ht="' . $RH . '"' : '') . '>' . $row . "</row>"; $ROWS[] = '<row r="' . $CUR_ROW . '"' . ($RH ? ' customHeight="1" ht="' . $RH . '"' : '') . '>' . $row . "</row>";
@ -716,7 +895,7 @@ class SimpleXLSXGen {
if (count($this->sheets[$idx]['hyperlinks'])) { if (count($this->sheets[$idx]['hyperlinks'])) {
$HYPERLINKS[] = '<hyperlinks>'; $HYPERLINKS[] = '<hyperlinks>';
foreach ($this->sheets[$idx]['hyperlinks'] as $h) { foreach ($this->sheets[$idx]['hyperlinks'] as $h) {
$HYPERLINKS[] = '<hyperlink ref="' . $h['R'] . '" r:id="' . $h['ID'] . '" location="' . $this->esc( $h['L'] ) . '" display="' . $this->esc( $h['H'] . ( $h['L'] ? ' - ' . $h['L'] : '' ) ) . '" />'; $HYPERLINKS[] = '<hyperlink ref="' . $h['R'] . '"' . ($h['ID'] ? ' r:id="' . $h['ID'] . '"' : '') . ' location="' . $this->esc($h['L']) . '" display="' . $this->esc($h['H'] . ($h['L'] ? ' - ' . $h['L'] : '')) . '" />';
} }
$HYPERLINKS[] = '</hyperlinks>'; $HYPERLINKS[] = '</hyperlinks>';
} }
@ -735,7 +914,8 @@ class SimpleXLSXGen {
$template); $template);
} }
public function num2name($num) { public function num2name($num)
{
$numeric = ($num - 1) % 26; $numeric = ($num - 1) % 26;
$letter = chr(65 + $numeric); $letter = chr(65 + $numeric);
$num2 = (int)(($num - 1) / 26); $num2 = (int)(($num - 1) / 26);
@ -745,7 +925,8 @@ class SimpleXLSXGen {
return $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; $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400;
@ -755,7 +936,9 @@ class SimpleXLSXGen {
// self::CALENDAR_WINDOWS_1900 // self::CALENDAR_WINDOWS_1900
$excel1900isLeapYear = True; $excel1900isLeapYear = True;
if (($year === 1900) && ($month <= 2)) { $excel1900isLeapYear = False; } if (($year === 1900) && ($month <= 2)) {
$excel1900isLeapYear = False;
}
$myExcelBaseDate = 2415020; $myExcelBaseDate = 2415020;
// Julian base date Adjustment // Julian base date Adjustment
@ -772,31 +955,61 @@ class SimpleXLSXGen {
return (float)$excelDate + $excelTime; return (float)$excelDate + $excelTime;
} }
public function setDefaultFont( $name ) {
public function setDefaultFont($name)
{
$this->defaultFont = $name; $this->defaultFont = $name;
return $this; return $this;
} }
public function setDefaultFontSize( $size ) {
public function setDefaultFontSize($size)
{
$this->defaultFontSize = $size; $this->defaultFontSize = $size;
return $this; return $this;
} }
public function autoFilter( $range ) { public function autoFilter($range)
{
$this->sheets[$this->curSheet]['autofilter'] = $range; $this->sheets[$this->curSheet]['autofilter'] = $range;
return $this; return $this;
} }
public function mergeCells( $range ) { public function mergeCells($range)
{
$this->sheets[$this->curSheet]['mergecells'][] = $range; $this->sheets[$this->curSheet]['mergecells'][] = $range;
return $this; return $this;
} }
public function setColWidth($col, $width) {
public function setColWidth($col, $width)
{
$this->sheets[$this->curSheet]['colwidth'][$col] = $width; $this->sheets[$this->curSheet]['colwidth'][$col] = $width;
return $this; return $this;
} }
public function esc( $str ) {
public function esc($str)
{
// XML UTF-8: #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] // XML UTF-8: #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
// but we use fast version // but we use fast version
return str_replace(['&', '<', '>', "\x00", "\x03", "\x0B"], ['&amp;', '&lt;', '&gt;', '', '', ''], $str); return str_replace(['&', '<', '>', "\x00", "\x03", "\x0B"], ['&amp;', '&lt;', '&gt;', '', '', ''], $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;
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 52 KiB