1
0
mirror of https://github.com/shuchkin/simplexlsxgen.git synced 2023-08-10 21:12:59 +03:00

Comments support

This commit is contained in:
Javier 2023-07-27 14:12:35 -03:00
parent 8b1cdddee3
commit 1d5713d681
2 changed files with 184 additions and 37 deletions

View File

@ -1,4 +1,5 @@
# SimpleXLSXGen # SimpleXLSXGen
[<img src="https://img.shields.io/github/license/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/blob/master/license.md) [<img src="https://img.shields.io/github/stars/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/stargazers) [<img src="https://img.shields.io/github/forks/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/network) [<img src="https://img.shields.io/github/issues/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/issues) [<img src="https://img.shields.io/github/license/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/blob/master/license.md) [<img src="https://img.shields.io/github/stars/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/stargazers) [<img src="https://img.shields.io/github/forks/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/network) [<img src="https://img.shields.io/github/issues/shuchkin/simplexlsxgen" />](https://github.com/shuchkin/simplexlsxgen/issues)
Export data to Excel XLSX file. PHP XLSX generator. No external tools and libraries. Export data to Excel XLSX file. PHP XLSX generator. No external tools and libraries.
@ -11,6 +12,7 @@ Export data to Excel XLSX file. PHP XLSX generator. No external tools and librar
*Hey, bro, please ★ the package for my motivation :) and [donate](https://opencollective.com/simplexlsx) for more motivation!* *Hey, bro, please ★ the package for my motivation :) and [donate](https://opencollective.com/simplexlsx) for more motivation!*
## Basic Usage ## Basic Usage
```php ```php
$books = [ $books = [
['ISBN', 'title', 'author', 'publisher', 'ctry' ], ['ISBN', 'title', 'author', 'publisher', 'ctry' ],
@ -23,6 +25,7 @@ $xlsx->saveAs('books.xlsx'); // or downloadAs('books.xlsx') or $xlsx_content = (
![XLSX screenshot](books.png) ![XLSX screenshot](books.png)
## Installation ## Installation
The recommended way to install this library is [through Composer](https://getcomposer.org). The recommended way to install this library is [through Composer](https://getcomposer.org).
[New to Composer?](https://getcomposer.org/doc/00-intro.md) [New to Composer?](https://getcomposer.org/doc/00-intro.md)
@ -33,6 +36,7 @@ $ composer require shuchkin/simplexlsxgen
or download class [here](https://github.com/shuchkin/simplexlsxgen/blob/master/src/SimpleXLSXGen.php) or download class [here](https://github.com/shuchkin/simplexlsxgen/blob/master/src/SimpleXLSXGen.php)
## Examples ## Examples
Use UTF-8 encoded strings. Use UTF-8 encoded strings.
### Data types ### Data types
@ -136,6 +140,9 @@ exit();
// Autofilter // Autofilter
$xlsx->autoFilter('A1:B10'); $xlsx->autoFilter('A1:B10');
// Comment cell
$xlsx->setComment("B2", "Comment text", "Comment author");
// Freeze rows and columns from top-left corner up to, but not including, // Freeze rows and columns from top-left corner up to, but not including,
// the row and column of the indicated cell (in A1 format as string or // the row and column of the indicated cell (in A1 format as string or
// R1C1 format as 2-element integer array) // R1C1 format as 2-element integer array)
@ -158,6 +165,7 @@ $xlsx->setAuthor('Sergey Shuchkin <sergey.shuchkin@gmail.com>')
->setCategory('This is Сategory') ->setCategory('This is Сategory')
->setApplication('Shuchkin\SimpleXLSXGen') ->setApplication('Shuchkin\SimpleXLSXGen')
``` ```
### JS array to Excel (AJAX) ### JS array to Excel (AJAX)
```php ```php
<?php // array2excel.php <?php // array2excel.php
@ -224,8 +232,11 @@ function array2excel() {
* When XLSX file is generated (__toString, saveAs or downloadAs methods), the data matrix of each sheet is traversed by rows and columns using foreach cycles. This implies freedom in the type of indexes used for the data but it also implies paying special attention when using numeric indexes since the array is traversed in the order in which they were defined and not in the numerical order of said indexes. * When XLSX file is generated (__toString, saveAs or downloadAs methods), the data matrix of each sheet is traversed by rows and columns using foreach cycles. This implies freedom in the type of indexes used for the data but it also implies paying special attention when using numeric indexes since the array is traversed in the order in which they were defined and not in the numerical order of said indexes.
* The helper methods for setting and getting the styles of a range or cell, implies that the data was defined as a 2-dimensional array (matrix) with consecutive 0-based numeric indexes. * The helper methods for setting and getting the styles of a range or cell, implies that the data was defined as a 2-dimensional array (matrix) with consecutive 0-based numeric indexes.
* If you use download or downloadAs methods, make sure you don't generate output before (not important if you use output buffering) and after you call the method. Pay special attention to whitespaces characters (space, cr/lf, tab) in source files before and after PHP code closing tags. If you inadvertently, or on purpose, generate text output, the resulting XLSX file will be corrupted. * If you use download or downloadAs methods, make sure you don't generate output before (not important if you use output buffering) and after you call the method. Pay special attention to whitespaces characters (space, cr/lf, tab) in source files before and after PHP code closing tags. If you inadvertently, or on purpose, generate text output, the resulting XLSX file will be corrupted.
* Methods setDefaultFont, setDefaultFontSize, setTitle, setSubject, setAuthor, setCompany, setManager, setKeywords, setDescription, setCategory, setApplication, setLastModifiedBy and rightToLeft apply to the entire book.
* Methods autoFilter, mergeCells, setColWidth, freezePanes and setComment apply to the current sheet (the last addSheet/fromArray used)
## Debug ## Debug
```php ```php
ini_set('error_reporting', E_ALL ); ini_set('error_reporting', E_ALL );
ini_set('display_errors', 1 ); ini_set('display_errors', 1 );

View File

@ -110,9 +110,9 @@ class SimpleXLSXGen
$this->defaultFont = 'Calibri'; $this->defaultFont = 'Calibri';
$this->defaultFontSize = 10; $this->defaultFontSize = 10;
$this->rtl = false; $this->rtl = false;
$this->sheets = [['name' => 'Sheet1', 'rows' => [], 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => '']]; $this->sheets = [];
$this->extLinkId = 0; $this->extLinkId = 0;
$this->SI = []; // sharedStrings index $this->SI = []; // sharedStrings index
$this->SI_KEYS = []; // & keys $this->SI_KEYS = []; // & keys
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_numFmts_topic_ID0E6KK6.html // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_numFmts_topic_ID0E6KK6.html
@ -179,18 +179,27 @@ class SimpleXLSXGen
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' . self::NEWLINE . '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' . self::NEWLINE .
'{RELS}' . self::NEWLINE . '{RELS}' . self::NEWLINE .
'</Relationships>', '</Relationships>',
'xl/drawings/vmlDrawing1.vml' => '<xml xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel">' . self::NEWLINE .
'<o:shapelayout v:ext="edit"><o:idmap v:ext="edit" data="1"/></o:shapelayout>' . self::NEWLINE .
'<v:shapetype id="_x0000_t202" coordsize="21600,21600" o:spt="202" path="m,l,21600r21600,l21600,xe"><v:stroke joinstyle="miter"/><v:path gradientshapeok="t" o:connecttype="rect"/></v:shapetype>' . self::NEWLINE .
'{SHAPES}</xml>',
'xl/worksheets/sheet1.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE . 'xl/worksheets/sheet1.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE .
'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' . self::NEWLINE . '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' . self::NEWLINE .
'<dimension ref="{REF}"/>' . self::NEWLINE . '<dimension ref="{REF}"/>' . self::NEWLINE .
'{SHEETVIEWS}' . self::NEWLINE . '{SHEETVIEWS}' . self::NEWLINE .
'{COLS}' . self::NEWLINE . '{COLS}' . self::NEWLINE .
'<sheetData>' . self::NEWLINE . '{ROWS}' . self::NEWLINE . '</sheetData>' . self::NEWLINE . '<sheetData>' . self::NEWLINE . '{ROWS}' . self::NEWLINE . '</sheetData>' . self::NEWLINE .
'{AUTOFILTER}' . '{MERGECELLS}' . self::NEWLINE . '{HYPERLINKS}' . self::NEWLINE . '{AUTOFILTER}' . '{MERGECELLS}' . self::NEWLINE . '{HYPERLINKS}' . self::NEWLINE . '{COMMENTS}' .
'</worksheet>', '</worksheet>',
'xl/worksheets/_rels/sheet1.xml.rels' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE . 'xl/worksheets/_rels/sheet1.xml.rels' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE .
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' . self::NEWLINE . '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' . self::NEWLINE .
'{HYPERLINKS}' . self::NEWLINE . '{HYPERLINKS}' . self::NEWLINE .
'</Relationships>', '</Relationships>',
'xl/comments1.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE .
'<comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">' . self::NEWLINE .
'{AUTHORS}' . self::NEWLINE .
'{COMMENTS}' . self::NEWLINE .
'</comments>',
'xl/sharedStrings.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE . 'xl/sharedStrings.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE .
'<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="{CNT}" uniqueCount="{CNT}">' . self::NEWLINE . '<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="{CNT}" uniqueCount="{CNT}">' . self::NEWLINE .
'{STRINGS}' . self::NEWLINE . '{STRINGS}' . self::NEWLINE .
@ -258,11 +267,23 @@ class SimpleXLSXGen
} }
} }
} }
$this->sheets[$this->curSheet] = ['name' => $name, 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => '', 'frozen' => '']; $this->sheets[$this->curSheet] = [
if (isset($rows[0]) && is_array($rows[0])) { 'name' => $name,
$this->sheets[$this->curSheet]['rows'] = $rows; 'rows' => [],
} else { 'hyperlinks' => [],
$this->sheets[$this->curSheet]['rows'] = []; 'mergecells' => [],
'colwidth' => [],
'autofilter' => '',
'frozen' => '',
'authors' => [],
'comments' => [],
'commentDrawingId' => null
];
if (is_array($rows)) {
foreach ($rows as $row) {
if (is_array($row)) $this->sheets[$this->curSheet]['rows'] = $rows;
break;
}
} }
return $this; return $this;
} }
@ -340,22 +361,22 @@ class SimpleXLSXGen
if ($cfilename === 'xl/_rels/workbook.xml.rels') { if ($cfilename === 'xl/_rels/workbook.xml.rels') {
$s = ''; $s = '';
for ($i = 0; $i < $cnt_sheets; $i++) { for ($i = 0; $i < $cnt_sheets; $i++) {
$s .= '<Relationship Id="rId' . ($i + 1) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"' . $s .= '<Relationship Id="rId' . ($i + 1) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" ' .
' Target="worksheets/sheet' . ($i + 1) . '.xml"/>' . self::NEWLINE; 'Target="worksheets/sheet' . ($i + 1) . '.xml"/>' . self::NEWLINE;
} }
$s .= '<Relationship Id="rId' . ($i + 1) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>' . self::NEWLINE; $s .= '<Relationship Id="rId' . ($cnt_sheets + 1) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>' . self::NEWLINE;
$s .= '<Relationship Id="rId' . ($i + 2) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>'; $s .= '<Relationship Id="rId' . ($cnt_sheets + 2) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>';
$template = str_replace('{RELS}', $s, $template); $template = str_replace('{RELS}', $s, $template);
$this->_writeEntry($fh, $cdrec, $cfilename, $template); $this->_writeEntry($fh, $cdrec, $cfilename, $template);
$entries++; $entries++;
} elseif ($cfilename === 'xl/workbook.xml') { } elseif ($cfilename === 'xl/workbook.xml') {
$s = ''; $s = '';
foreach ($this->sheets as $k => $v) { foreach ($this->sheets as $k => $v) {
$s .= '<sheet name="' . $this->esc($v['name']) . '" sheetId="' . ($k + 1) . '" r:id="rId' . ($k + 1) . '"/>'; $s .= '<sheet name="' . self::esc($v['name']) . '" sheetId="' . ($k + 1) . '" r:id="rId' . ($k + 1) . '"/>';
} }
$template = str_replace( $template = str_replace(
['{SHEETS}', '{APP}'], ['{SHEETS}', '{APP}'],
[$s, $this->esc($this->application)], [$s, self::esc($this->application)],
$template $template
); );
$this->_writeEntry($fh, $cdrec, $cfilename, $template); $this->_writeEntry($fh, $cdrec, $cfilename, $template);
@ -363,7 +384,7 @@ class SimpleXLSXGen
} elseif ($cfilename === 'docProps/app.xml') { } elseif ($cfilename === 'docProps/app.xml') {
$template = str_replace( $template = str_replace(
['{APP}', '{COMPANY}', '{MANAGER}'], ['{APP}', '{COMPANY}', '{MANAGER}'],
[$this->esc($this->application), $this->esc($this->company), $this->esc($this->manager)], [self::esc($this->application), self::esc($this->company), self::esc($this->manager)],
$template $template
); );
$this->_writeEntry($fh, $cdrec, $cfilename, $template); $this->_writeEntry($fh, $cdrec, $cfilename, $template);
@ -371,7 +392,7 @@ class SimpleXLSXGen
} elseif ($cfilename === 'docProps/core.xml') { } elseif ($cfilename === 'docProps/core.xml') {
$template = str_replace( $template = str_replace(
['{DATE}', '{AUTHOR}', '{TITLE}', '{SUBJECT}', '{KEYWORD}', '{DESCRIPTION}', '{CATEGORY}', '{LAST_MODIFY_BY}'], ['{DATE}', '{AUTHOR}', '{TITLE}', '{SUBJECT}', '{KEYWORD}', '{DESCRIPTION}', '{CATEGORY}', '{LAST_MODIFY_BY}'],
[gmdate('Y-m-d\TH:i:s\Z'), $this->esc($this->author), $this->esc($this->title), $this->esc($this->subject), $this->esc($this->keywords), $this->esc($this->description), $this->esc($this->category), $this->esc($this->lastModifiedBy)], [gmdate('Y-m-d\TH:i:s\Z'), self::esc($this->author), self::esc($this->title), self::esc($this->subject), self::esc($this->keywords), self::esc($this->description), self::esc($this->category), self::esc($this->lastModifiedBy)],
$template $template
); );
$this->_writeEntry($fh, $cdrec, $cfilename, $template); $this->_writeEntry($fh, $cdrec, $cfilename, $template);
@ -395,6 +416,70 @@ class SimpleXLSXGen
$entries++; $entries++;
} }
$xml = null; $xml = null;
} elseif ($cfilename === 'xl/comments1.xml') {
foreach ($this->sheets as $k => $v) {
$cComments = count($v['comments']);
$cAuthors = count($v['authors']);
if ($cComments) {
$filename = 'xl/comments' . ($k + 1) . '.xml';
//create author(s) list and set author Id
$authorId = -1; //index for document author to use in comments
$AUTHORS = '<authors>' . self::NEWLINE;
for ($i = 0; $i < $cAuthors; $i++) {
$AUTHORS .= ' <author>' . $v['authors'][$i] . '</author>' . self::NEWLINE;
if ($v['authors'][$i] === __CLASS__) $authorId = $i;
}
if ($authorId === -1) {
$authorId = $cAuthors;
$AUTHORS .= ' <author>' . __CLASS__ . '</author>' . self::NEWLINE;
}
$AUTHORS .= '</authors>';
//comments list
$COMMENTS = '<commentList>' . self::NEWLINE;
for ($i = 0; $i < $cComments; $i++) {
$COMMENTS .= '<comment ref="' . $v['comments'][$i]['cell'] . '" authorId="' . (($v['comments'][$i]['authorId'] !== -1) ? $v['comments'][$i]['authorId'] : $authorId) . '" shapeId="0"><text>' . self::NEWLINE;
//prefix with author name if exist
if ($v['comments'][$i]['authorId'] !== -1) {
$COMMENTS .= ' <r><rPr><b></b><sz val="9"/><rFont val="Tahoma"/><charset val="1"/></rPr><t xml:space="preserve">' . $v['authors'][$v['comments'][$i]['authorId']] . ':' . self::NEWLINE . '</t></r>';
}
$preserve = (strpbrk($v['comments'][$i]['comment'], "\t\r\n") !== false) ? ' xml:space="preserve"' : '';
$COMMENTS .= ' <r><rPr><sz val="9"/><rFont val="Tahoma"/><charset val="1"/></rPr><t' . $preserve . '>' . $v['comments'][$i]['comment'] . '</t></r>' . self::NEWLINE;
$COMMENTS .= '</text></comment>' . self::NEWLINE;
}
$COMMENTS .= '</commentList>';
$xml = str_replace(['{AUTHORS}', '{COMMENTS}'], [$AUTHORS, $COMMENTS], $template);
$this->_writeEntry($fh, $cdrec, $filename, $xml);
$entries++;
}
}
$xml = null;
} elseif ($cfilename === 'xl/drawings/vmlDrawing1.vml') {
foreach ($this->sheets as $k => $v) {
$cComments = count($v['comments']);
if ($cComments) {
$SHAPES = '';
$filename = 'xl/drawings/vmlDrawing' . ($k + 1) . '.vml';
for ($i = 0; $i < $cComments; $i++) {
[$row, $col] = self::cell2coord($v['comments'][$i]['cell']);
$SHAPES .= '<v:shape id="_x0000_s' . $v['comments'][$i]['cell'] . '" type="#_x0000_t202" style="z-index:2;visibility:hidden" fillcolor="#ffffe1">' . self::NEWLINE .
' <v:fill color2="#ffffe1"/>' . self::NEWLINE .
' <v:path o:connecttype="none"/>' . self::NEWLINE .
' <x:ClientData ObjectType="Note">' . self::NEWLINE .
' <x:MoveWithCells/>' . self::NEWLINE .
' <x:SizeWithCells/>' . self::NEWLINE .
' <x:Anchor>'.$col.', 0, '.($row-2).', 0, '.($col+2).', 0, '.($row+1).', 0</x:Anchor>' . self::NEWLINE .
' <x:AutoFill>False</x:AutoFill>' . self::NEWLINE .
' <x:Row>'.($row-1).'</x:Row>' . self::NEWLINE .
' <x:Column>'.($col-1).'</x:Column>' . self::NEWLINE .
' </x:ClientData>' . self::NEWLINE .
'</v:shape>' . self::NEWLINE;
}
$xml = str_replace('{SHAPES}', $SHAPES, $template);
$this->_writeEntry($fh, $cdrec, $filename, $xml);
$entries++;
}
}
$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 ($this->extLinkId) { if ($this->extLinkId) {
@ -402,9 +487,13 @@ class SimpleXLSXGen
$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']) { 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="' . self::esc($h['H']) . '" TargetMode="External"/>';
} }
} }
if ($v['commentDrawingId']) {
$RH[] = ' <Relationship Id="rId' . $v['commentDrawingId'] . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" Target="../drawings/vmlDrawing' . ($k + 1) . '.vml"/>';
$RH[] = ' <Relationship Id="rId' . ($v['commentDrawingId'] + 1) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="../comments' . ($k + 1) . '.xml"/>';
}
$xml = str_replace('{HYPERLINKS}', implode(self::NEWLINE, $RH), $template); $xml = str_replace('{HYPERLINKS}', implode(self::NEWLINE, $RH), $template);
$this->_writeEntry($fh, $cdrec, $filename, $xml); $this->_writeEntry($fh, $cdrec, $filename, $xml);
$entries++; $entries++;
@ -412,13 +501,20 @@ class SimpleXLSXGen
} }
$xml = null; $xml = null;
} elseif ($cfilename === '[Content_Types].xml') { } elseif ($cfilename === '[Content_Types].xml') {
$hasComments = false;
//if $TYPES is initialized always with this value, why not add it to the template?
$TYPES = ['<Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>']; $TYPES = ['<Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'];
foreach ($this->sheets as $k => $v) { foreach ($this->sheets as $k => $v) {
$TYPES[] = '<Override PartName="/xl/worksheets/sheet' . ($k + 1) . '.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'; $TYPES[] = '<Override PartName="/xl/worksheets/sheet' . ($k + 1) . '.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>';
if ($this->extLinkId) { 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"/>';
} }
if (count($v['comments'])) {
$hasComments = true;
$TYPES[] = '<Override PartName="/xl/comments' . ($k + 1) . '.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>';
}
} }
if ($hasComments) $TYPES[] = '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>';
$template = str_replace('{TYPES}', implode(self::NEWLINE, $TYPES), $template); $template = str_replace('{TYPES}', implode(self::NEWLINE, $TYPES), $template);
$this->_writeEntry($fh, $cdrec, $cfilename, $template); $this->_writeEntry($fh, $cdrec, $cfilename, $template);
$entries++; $entries++;
@ -499,7 +595,7 @@ class SimpleXLSXGen
$ba[] = $ba[0]; $ba[] = $ba[0];
} }
if (!isset($ba[4])) { // diagonal if (!isset($ba[4])) { // diagonal
$ba[] = 'none'; $ba[] = 'none';
} }
$sides = ['left' => 3, 'right' => 1, 'top' => 0, 'bottom' => 2, 'diagonal' => 4]; $sides = ['left' => 3, 'right' => 1, 'top' => 0, 'bottom' => 2, 'diagonal' => 4];
foreach ($sides as $side => $idx) { foreach ($sides as $side => $idx) {
@ -710,7 +806,7 @@ class SimpleXLSXGen
} }
$cname = $this->num2name($CUR_COL) . $CUR_ROW; $cname = $this->num2name($CUR_COL) . $CUR_ROW;
if ($v === null || $v === '') { if ($v === null || $v === '') {
$row .= ' <c r="' . $cname . '"/>'; $row .= ' <c r="' . $cname . '"/>';
continue; continue;
} }
$ct = $cv = $cf = null; $ct = $cv = $cf = null;
@ -873,7 +969,7 @@ class SimpleXLSXGen
} }
} }
if ($cv === null) { if ($cv === null) {
$v = $this->esc($v); $v = self::esc($v);
if ($cf) { if ($cf) {
$ct = 'str'; $ct = 'str';
$cv = $v; $cv = $v;
@ -963,16 +1059,23 @@ 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'] . '"' . ($h['ID'] ? ' 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="' . self::esc($h['L']) . '" display="' . self::esc($h['H'] . ($h['L'] ? ' - ' . $h['L'] : '')) . '"/>';
} }
$HYPERLINKS[] = '</hyperlinks>'; $HYPERLINKS[] = '</hyperlinks>';
} }
$COMMENTS = '';
if (count($this->sheets[$idx]['comments'])) {
$this->extLinkId++;
$this->sheets[$idx]['commentDrawingId'] = $this->extLinkId;
$COMMENTS = '<legacyDrawing r:id="rId' . $this->extLinkId . '"/>' . self::NEWLINE;
}
//restore locale //restore locale
setlocale(LC_NUMERIC, $_loc); setlocale(LC_NUMERIC, $_loc);
return str_replace( return str_replace(
['{REF}', '{COLS}', '{ROWS}', '{AUTOFILTER}', '{MERGECELLS}', '{HYPERLINKS}', '{SHEETVIEWS}'], ['{REF}', '{COLS}', '{ROWS}', '{AUTOFILTER}', '{MERGECELLS}', '{HYPERLINKS}', '{SHEETVIEWS}', '{COMMENTS}'],
[ [
$REF, $REF,
implode(self::NEWLINE, $COLS), implode(self::NEWLINE, $COLS),
@ -980,7 +1083,8 @@ class SimpleXLSXGen
$AUTOFILTER, $AUTOFILTER,
implode(self::NEWLINE, $MERGECELLS), implode(self::NEWLINE, $MERGECELLS),
implode(self::NEWLINE, $HYPERLINKS), implode(self::NEWLINE, $HYPERLINKS),
$SHEETVIEWS $SHEETVIEWS,
$COMMENTS
], ],
$template $template
); );
@ -1089,6 +1193,11 @@ class SimpleXLSXGen
$this->lastModifiedBy = $lastModifiedBy; $this->lastModifiedBy = $lastModifiedBy;
return $this; return $this;
} }
public function rightToLeft($value = true)
{
$this->rtl = $value;
return $this;
}
public function autoFilter($range) public function autoFilter($range)
{ {
@ -1105,18 +1214,44 @@ class SimpleXLSXGen
$this->sheets[$this->curSheet]['colwidth'][$col] = $width; $this->sheets[$this->curSheet]['colwidth'][$col] = $width;
return $this; return $this;
} }
public function freezePanes($cell)
public function rightToLeft($value = true)
{ {
$this->rtl = $value; $this->sheets[$this->curSheet]['frozen'] = $cell;
return $this; return $this;
} }
/**
public function esc($str) * Set comment of a cell of the current sheet
*
* @param string|array $cell Cell reference in A1 format (string) or R1C1 format (2-element array)
* @param string $comment Text comment
* @param string $author Optional. If specified, the comment will have $author as a preffix (in bold) of comment
*
* @return void
*/
public function setComment($cell, $comment, $author = '')
{ {
// XML UTF-8: #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] if (is_array($cell)) {
// but we use fast version $cell = self::coord2cell($cell);
return str_replace(['&', '<', '>', "\x00", "\x03", "\x0B"], ['&amp;', '&lt;', '&gt;', '', '', ''], $str); } else {
$cell = trim(strtoupper($cell));
}
//search author index
$index = -1;
if ($author !== '') {
$cAuthors = count($this->sheets[$this->curSheet]['authors']);
for ($i = 0; $i < $cAuthors; $i++) {
if ($author === $this->sheets[$this->curSheet]['authors'][$i]) {
$index = $i;
break;
}
}
if ($index === -1) {
$this->sheets[$this->curSheet]['authors'][] = $author;
$index = $cAuthors;
}
}
$this->sheets[$this->curSheet]['comments'][] = ['cell' => $cell, 'comment' => $comment, 'authorId' => $index];
return $this;
} }
public function getNumFmtId($code) public function getNumFmtId($code)
@ -1133,17 +1268,18 @@ class SimpleXLSXGen
return $id; return $id;
} }
public static 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"], ['&amp;', '&lt;', '&gt;', '', '', ''], $str);
}
public static function raw($value) public static function raw($value)
{ {
return "\0" . $value; return "\0" . $value;
} }
public function freezePanes($cell)
{
$this->sheets[$this->curSheet]['frozen'] = $cell;
return $this;
}
/** /**
* Convert A1 cell reference format to R1C1 cell reference format (row/col number starting from 1) * Convert A1 cell reference format to R1C1 cell reference format (row/col number starting from 1)
* *