Merge 7ba94991dd
into 306aa7dad9
This commit is contained in:
commit
f47f9cbda8
25
README.md
25
README.md
|
@ -1,4 +1,5 @@
|
|||
# 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)
|
||||
|
||||
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!*
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```php
|
||||
$books = [
|
||||
['ISBN', 'title', 'author', 'publisher', 'ctry' ],
|
||||
|
@ -23,6 +25,7 @@ $xlsx->saveAs('books.xlsx'); // or downloadAs('books.xlsx') or $xlsx_content = (
|
|||
![XLSX screenshot](books.png)
|
||||
|
||||
## Installation
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
|
@ -33,7 +36,9 @@ $ composer require shuchkin/simplexlsxgen
|
|||
or download class [here](https://github.com/shuchkin/simplexlsxgen/blob/master/src/SimpleXLSXGen.php)
|
||||
|
||||
## Examples
|
||||
|
||||
Use UTF-8 encoded strings.
|
||||
|
||||
### Data types
|
||||
```php
|
||||
$data = [
|
||||
|
@ -127,16 +132,20 @@ $xlsx_cache = (string) (new Shuchkin\SimpleXLSXGen)->addSheet( $books, 'Modern s
|
|||
// Classic interface
|
||||
use Shuchkin\SimpleXLSXGen
|
||||
$xlsx = new SimpleXLSXGen();
|
||||
$xlsx->addSheet( $books, 'Catalog 2021' );
|
||||
$xlsx->addSheet( $books2, 'Stephen King catalog');
|
||||
$xlsx->addSheet($books, 'Catalog 2021');
|
||||
$xlsx->addSheet($books2, 'Stephen King catalog');
|
||||
$xlsx->downloadAs('books_2021.xlsx');
|
||||
exit();
|
||||
|
||||
// Autofilter
|
||||
$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,
|
||||
// the row and column of the indicated cell
|
||||
// the row and column of the indicated cell (in A1 format as string or
|
||||
// R1C1 format as 2-element integer array)
|
||||
$xlsx->freezePanes('C3');
|
||||
|
||||
// RTL mode
|
||||
|
@ -156,6 +165,7 @@ $xlsx->setAuthor('Sergey Shuchkin <sergey.shuchkin@gmail.com>')
|
|||
->setCategory('This is Сategory')
|
||||
->setApplication('Shuchkin\SimpleXLSXGen')
|
||||
```
|
||||
|
||||
### JS array to Excel (AJAX)
|
||||
```php
|
||||
<?php // array2excel.php
|
||||
|
@ -217,7 +227,16 @@ function array2excel() {
|
|||
</html>
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
* 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.
|
||||
* 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
|
||||
|
||||
```php
|
||||
ini_set('error_reporting', E_ALL );
|
||||
ini_set('display_errors', 1 );
|
||||
|
|
|
@ -3,15 +3,16 @@
|
|||
/** @noinspection ReturnTypeCanBeDeclaredInspection */
|
||||
/** @noinspection PhpMissingReturnTypeInspection */
|
||||
/** @noinspection NullCoalescingOperatorCanBeUsedInspection */
|
||||
|
||||
/** @noinspection PhpIssetCanBeReplacedWithCoalesceInspection */
|
||||
|
||||
namespace Shuchkin;
|
||||
|
||||
/**
|
||||
* Class SimpleXLSXGen
|
||||
* Export data to MS Excel. PHP XLSX generator
|
||||
* Author: sergey.shuchkin@gmail.com
|
||||
*
|
||||
* Export data to MS Excel. PHP XLSX generator.
|
||||
*
|
||||
* @author Sergey Shuchkin <sergey.shuchkin@gmail.com>
|
||||
*/
|
||||
class SimpleXLSXGen
|
||||
{
|
||||
|
@ -40,6 +41,7 @@ class SimpleXLSXGen
|
|||
protected $keywords;
|
||||
protected $category;
|
||||
protected $lastModifiedBy;
|
||||
|
||||
const N_NORMAL = 0; // General
|
||||
const N_INT = 1; // 0
|
||||
const N_DEC = 2; // 0.00
|
||||
|
@ -89,6 +91,8 @@ class SimpleXLSXGen
|
|||
const B_MEDIUM_DASH_DOT_DOT = 12;
|
||||
const B_SLANT_DASH_DOT = 13;
|
||||
|
||||
const NEWLINE = "\r\n";
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->subject = '';
|
||||
|
@ -106,9 +110,9 @@ class SimpleXLSXGen
|
|||
$this->defaultFont = 'Calibri';
|
||||
$this->defaultFontSize = 10;
|
||||
$this->rtl = false;
|
||||
$this->sheets = [['name' => 'Sheet1', 'rows' => [], 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => '']];
|
||||
$this->sheets = [];
|
||||
$this->extLinkId = 0;
|
||||
$this->SI = []; // sharedStrings index
|
||||
$this->SI = []; // sharedStrings index
|
||||
$this->SI_KEYS = []; // & keys
|
||||
|
||||
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_numFmts_topic_ID0E6KK6.html
|
||||
|
@ -142,78 +146,92 @@ class SimpleXLSXGen
|
|||
];
|
||||
$this->XF_KEYS[implode('-', $this->XF[0])] = 0; // & keys
|
||||
$this->XF_KEYS[implode('-', $this->XF[1])] = 1;
|
||||
|
||||
$this->template = [
|
||||
'_rels/.rels' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
|
||||
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
|
||||
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
|
||||
</Relationships>',
|
||||
'docProps/app.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
|
||||
<TotalTime>0</TotalTime>
|
||||
<Application>{APP}</Application>
|
||||
<Company>{COMPANY}</Company>
|
||||
<Manager>{MANAGER}</Manager>
|
||||
</Properties>',
|
||||
'docProps/core.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<dcterms:created xsi:type="dcterms:W3CDTF">{DATE}</dcterms:created>
|
||||
<dc:title>{TITLE}</dc:title>
|
||||
<dc:subject>{SUBJECT}</dc:subject>
|
||||
<dc:creator>{AUTHOR}</dc:creator>
|
||||
<cp:lastModifiedBy>{LAST_MODIFY_BY}</cp:lastModifiedBy>
|
||||
<cp:keywords>{KEYWORD}</cp:keywords>
|
||||
<dc:description>{DESCRIPTION}</dc:description>
|
||||
<cp:category>{CATEGORY}</cp:category>
|
||||
<dc:language>en-US</dc:language>
|
||||
<dcterms:modified xsi:type="dcterms:W3CDTF">{DATE}</dcterms:modified>
|
||||
<cp:revision>1</cp:revision>
|
||||
</cp:coreProperties>',
|
||||
'xl/_rels/workbook.xml.rels' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
{RELS}
|
||||
</Relationships>',
|
||||
'xl/worksheets/sheet1.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
||||
<dimension ref="{REF}"/>
|
||||
{SHEETVIEWS}
|
||||
{COLS}
|
||||
<sheetData>{ROWS}</sheetData>
|
||||
{AUTOFILTER}{MERGECELLS}{HYPERLINKS}
|
||||
</worksheet>',
|
||||
'xl/worksheets/_rels/sheet1.xml.rels' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">{HYPERLINKS}</Relationships>',
|
||||
'xl/sharedStrings.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<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"?>
|
||||
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||
{NUMFMTS}
|
||||
{FONTS}
|
||||
{FILLS}
|
||||
{BORDERS}
|
||||
<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" /></cellStyleXfs>
|
||||
{XF}
|
||||
<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>
|
||||
</styleSheet>',
|
||||
'xl/workbook.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
||||
<fileVersion appName="{APP}"/>
|
||||
<sheets>
|
||||
{SHEETS}
|
||||
</sheets>
|
||||
</workbook>',
|
||||
'[Content_Types].xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
<Override PartName="/rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||
<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
|
||||
<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
|
||||
<Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||
<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
|
||||
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
|
||||
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
||||
{TYPES}
|
||||
</Types>',
|
||||
'_rels/.rels' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE .
|
||||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' . self::NEWLINE .
|
||||
'<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' . self::NEWLINE .
|
||||
'<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>' . self::NEWLINE .
|
||||
'<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>' . self::NEWLINE .
|
||||
'</Relationships>',
|
||||
'docProps/app.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE .
|
||||
'<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">' . self::NEWLINE .
|
||||
'<TotalTime>0</TotalTime>' . self::NEWLINE .
|
||||
'<Application>{APP}</Application>' . self::NEWLINE .
|
||||
'<Company>{COMPANY}</Company>' . self::NEWLINE .
|
||||
'<Manager>{MANAGER}</Manager>' . self::NEWLINE .
|
||||
'</Properties>',
|
||||
'docProps/core.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE .
|
||||
'<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . self::NEWLINE .
|
||||
'<dcterms:created xsi:type="dcterms:W3CDTF">{DATE}</dcterms:created>' . self::NEWLINE .
|
||||
'<dc:title>{TITLE}</dc:title>' . self::NEWLINE .
|
||||
'<dc:subject>{SUBJECT}</dc:subject>' . self::NEWLINE .
|
||||
'<dc:creator>{AUTHOR}</dc:creator>' . self::NEWLINE .
|
||||
'<cp:lastModifiedBy>{LAST_MODIFY_BY}</cp:lastModifiedBy>' . self::NEWLINE .
|
||||
'<cp:keywords>{KEYWORD}</cp:keywords>' . self::NEWLINE .
|
||||
'<dc:description>{DESCRIPTION}</dc:description>' . self::NEWLINE .
|
||||
'<cp:category>{CATEGORY}</cp:category>' . self::NEWLINE .
|
||||
'<dc:language>en-US</dc:language>' . self::NEWLINE .
|
||||
'<dcterms:modified xsi:type="dcterms:W3CDTF">{DATE}</dcterms:modified>' . self::NEWLINE .
|
||||
'<cp:revision>1</cp:revision>' . self::NEWLINE .
|
||||
'</cp:coreProperties>',
|
||||
'xl/_rels/workbook.xml.rels' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE .
|
||||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' . self::NEWLINE .
|
||||
'{RELS}' . self::NEWLINE .
|
||||
'</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 .
|
||||
'<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 .
|
||||
'{SHEETVIEWS}' . self::NEWLINE .
|
||||
'{COLS}' . self::NEWLINE .
|
||||
'<sheetData>' . self::NEWLINE . '{ROWS}' . self::NEWLINE . '</sheetData>' . self::NEWLINE .
|
||||
'{AUTOFILTER}' . '{MERGECELLS}' . self::NEWLINE . '{HYPERLINKS}' . self::NEWLINE . '{COMMENTS}' .
|
||||
'</worksheet>',
|
||||
'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 .
|
||||
'{HYPERLINKS}' . self::NEWLINE .
|
||||
'</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 .
|
||||
'<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="{CNT}" uniqueCount="{CNT}">' . self::NEWLINE .
|
||||
'{STRINGS}' . self::NEWLINE .
|
||||
'</sst>',
|
||||
'xl/styles.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE .
|
||||
'<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">' . self::NEWLINE .
|
||||
'{NUMFMTS}' . self::NEWLINE .
|
||||
'{FONTS}' . self::NEWLINE .
|
||||
'{FILLS}' . self::NEWLINE .
|
||||
'{BORDERS}' . self::NEWLINE .
|
||||
'<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" /></cellStyleXfs>' . self::NEWLINE .
|
||||
'{XF}' . self::NEWLINE .
|
||||
'<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>' . self::NEWLINE .
|
||||
'</styleSheet>',
|
||||
'xl/workbook.xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE .
|
||||
'<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' . self::NEWLINE .
|
||||
'<fileVersion appName="{APP}"/>' . self::NEWLINE .
|
||||
'<sheets>' . self::NEWLINE .
|
||||
'{SHEETS}' . self::NEWLINE .
|
||||
'</sheets>' . self::NEWLINE .
|
||||
'</workbook>',
|
||||
'[Content_Types].xml' => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . self::NEWLINE .
|
||||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' . self::NEWLINE .
|
||||
'<Override PartName="/rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' . self::NEWLINE .
|
||||
'<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>' . self::NEWLINE .
|
||||
'<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>' . self::NEWLINE .
|
||||
'<Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' . self::NEWLINE .
|
||||
'<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>' . self::NEWLINE .
|
||||
'<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>' . self::NEWLINE .
|
||||
'<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>' . self::NEWLINE .
|
||||
'{TYPES}' . self::NEWLINE .
|
||||
'</Types>',
|
||||
];
|
||||
// <col min="1" max="1" width="22.1796875" bestFit="1" customWidth="1"/>
|
||||
// <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>
|
||||
|
@ -249,11 +267,23 @@ class SimpleXLSXGen
|
|||
}
|
||||
}
|
||||
}
|
||||
$this->sheets[$this->curSheet] = ['name' => $name, 'hyperlinks' => [], 'mergecells' => [], 'colwidth' => [], 'autofilter' => '', 'frozen' => ''];
|
||||
if (isset($rows[0]) && is_array($rows[0])) {
|
||||
$this->sheets[$this->curSheet]['rows'] = $rows;
|
||||
} else {
|
||||
$this->sheets[$this->curSheet]['rows'] = [];
|
||||
$this->sheets[$this->curSheet] = [
|
||||
'name' => $name,
|
||||
'rows' => [],
|
||||
'hyperlinks' => [],
|
||||
'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;
|
||||
}
|
||||
|
@ -311,7 +341,8 @@ class SimpleXLSXGen
|
|||
ob_end_clean();
|
||||
}
|
||||
fseek($fh, 0);
|
||||
fpassthru($fh);
|
||||
if (function_exists('fpassthru')) fpassthru($fh);
|
||||
else echo stream_get_contents($fh);
|
||||
fclose($fh);
|
||||
return true;
|
||||
}
|
||||
|
@ -330,44 +361,51 @@ class SimpleXLSXGen
|
|||
if ($cfilename === 'xl/_rels/workbook.xml.rels') {
|
||||
$s = '';
|
||||
for ($i = 0; $i < $cnt_sheets; $i++) {
|
||||
$s .= '<Relationship Id="rId' . ($i + 1) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"' .
|
||||
' Target="worksheets/sheet' . ($i + 1) . ".xml\"/>\r\n";
|
||||
$s .= '<Relationship Id="rId' . ($i + 1) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" ' .
|
||||
'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"/>' . "\r\n";
|
||||
$s .= '<Relationship Id="rId' . ($i + 2) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>';
|
||||
|
||||
$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' . ($cnt_sheets + 2) . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>';
|
||||
$template = str_replace('{RELS}', $s, $template);
|
||||
$this->_writeEntry($fh, $cdrec, $cfilename, $template);
|
||||
$entries++;
|
||||
} elseif ($cfilename === 'xl/workbook.xml') {
|
||||
$s = '';
|
||||
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) . '"/>';
|
||||
}
|
||||
$search = ['{SHEETS}', '{APP}'];
|
||||
$replace = [$s, $this->esc($this->application)];
|
||||
$template = str_replace($search, $replace, $template);
|
||||
$template = str_replace(
|
||||
['{SHEETS}', '{APP}'],
|
||||
[$s, self::esc($this->application)],
|
||||
$template
|
||||
);
|
||||
$this->_writeEntry($fh, $cdrec, $cfilename, $template);
|
||||
$entries++;
|
||||
} elseif ($cfilename === 'docProps/app.xml') {
|
||||
$search = ['{APP}', '{COMPANY}', '{MANAGER}'];
|
||||
$replace = [$this->esc($this->application), $this->esc($this->company), $this->esc($this->manager)];
|
||||
$template = str_replace($search, $replace, $template);
|
||||
$template = str_replace(
|
||||
['{APP}', '{COMPANY}', '{MANAGER}'],
|
||||
[self::esc($this->application), self::esc($this->company), self::esc($this->manager)],
|
||||
$template
|
||||
);
|
||||
$this->_writeEntry($fh, $cdrec, $cfilename, $template);
|
||||
$entries++;
|
||||
} elseif ($cfilename === 'docProps/core.xml') {
|
||||
$search = ['{DATE}', '{AUTHOR}', '{TITLE}', '{SUBJECT}', '{KEYWORD}', '{DESCRIPTION}', '{CATEGORY}', '{LAST_MODIFY_BY}'];
|
||||
$replace = [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)];
|
||||
$template = str_replace($search, $replace, $template);
|
||||
$template = str_replace(
|
||||
['{DATE}', '{AUTHOR}', '{TITLE}', '{SUBJECT}', '{KEYWORD}', '{DESCRIPTION}', '{CATEGORY}', '{LAST_MODIFY_BY}'],
|
||||
[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
|
||||
);
|
||||
$this->_writeEntry($fh, $cdrec, $cfilename, $template);
|
||||
$entries++;
|
||||
} elseif ($cfilename === 'xl/sharedStrings.xml') {
|
||||
if (!count($this->SI)) {
|
||||
$this->SI[] = 'No Data';
|
||||
}
|
||||
$si_cnt = count($this->SI);
|
||||
$si = '<si><t>' . implode("</t></si>\r\n<si><t>", $this->SI) . '</t></si>';
|
||||
$template = str_replace(['{CNT}', '{STRINGS}'], [$si_cnt, $si], $template);
|
||||
$template = str_replace(
|
||||
['{CNT}', '{STRINGS}'],
|
||||
[count($this->SI), '<si><t>' . implode('</t></si>' . self::NEWLINE . '<si><t>', $this->SI) . '</t></si>'],
|
||||
$template
|
||||
);
|
||||
$this->_writeEntry($fh, $cdrec, $cfilename, $template);
|
||||
$entries++;
|
||||
} elseif ($cfilename === 'xl/worksheets/sheet1.xml') {
|
||||
|
@ -378,6 +416,70 @@ class SimpleXLSXGen
|
|||
$entries++;
|
||||
}
|
||||
$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 . ', 0, ' . ($col+2) . ', 0, ' . ($row+3) . ', 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') {
|
||||
foreach ($this->sheets as $k => $v) {
|
||||
if ($this->extLinkId) {
|
||||
|
@ -385,24 +487,35 @@ class SimpleXLSXGen
|
|||
$filename = 'xl/worksheets/_rels/sheet' . ($k + 1) . '.xml.rels';
|
||||
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="' . self::esc($h['H']) . '" TargetMode="External"/>';
|
||||
}
|
||||
}
|
||||
$xml = str_replace('{HYPERLINKS}', implode("\r\n", $RH), $template);
|
||||
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);
|
||||
$this->_writeEntry($fh, $cdrec, $filename, $xml);
|
||||
$entries++;
|
||||
}
|
||||
}
|
||||
$xml = null;
|
||||
} 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"/>'];
|
||||
foreach ($this->sheets as $k => $v) {
|
||||
$TYPES[] = '<Override PartName="/xl/worksheets/sheet' . ($k + 1) . '.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>';
|
||||
if ($this->extLinkId) {
|
||||
$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"/>';
|
||||
}
|
||||
}
|
||||
$template = str_replace('{TYPES}', implode("\r\n", $TYPES), $template);
|
||||
if ($hasComments) $TYPES[] = '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>';
|
||||
$template = str_replace('{TYPES}', implode(self::NEWLINE, $TYPES), $template);
|
||||
$this->_writeEntry($fh, $cdrec, $cfilename, $template);
|
||||
$entries++;
|
||||
} elseif ($cfilename === 'xl/styles.xml') {
|
||||
|
@ -465,7 +578,6 @@ class SimpleXLSXGen
|
|||
if ($xf[1] & self::A_WRAPTEXT) {
|
||||
$align .= ' wrapText="1"';
|
||||
}
|
||||
|
||||
// border
|
||||
$BR_ID = 0;
|
||||
if ($xf[6] !== '') {
|
||||
|
@ -483,7 +595,7 @@ class SimpleXLSXGen
|
|||
$ba[] = $ba[0];
|
||||
}
|
||||
if (!isset($ba[4])) { // diagonal
|
||||
$ba[] = 'none';
|
||||
$ba[] = 'none';
|
||||
}
|
||||
$sides = ['left' => 3, 'right' => 1, 'top' => 0, 'bottom' => 2, 'diagonal' => 4];
|
||||
foreach ($sides as $side => $idx) {
|
||||
|
@ -534,7 +646,7 @@ class SimpleXLSXGen
|
|||
|
||||
$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)],
|
||||
[implode(self::NEWLINE, $NF), implode(self::NEWLINE, $FONTS), implode(self::NEWLINE, $XF), implode(self::NEWLINE, $FILLS), implode(self::NEWLINE, $BR)],
|
||||
$template
|
||||
);
|
||||
$this->_writeEntry($fh, $cdrec, $cfilename, $template);
|
||||
|
@ -556,7 +668,6 @@ class SimpleXLSXGen
|
|||
fwrite($fh, pack('V', $before_cd)); // offset to start of central dir
|
||||
fwrite($fh, pack('v', mb_strlen($zipComments, '8bit'))); // .zip file comment length
|
||||
fwrite($fh, $zipComments);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -642,24 +753,19 @@ class SimpleXLSXGen
|
|||
setlocale(LC_NUMERIC, 'C');
|
||||
$COLS = [];
|
||||
$ROWS = [];
|
||||
// $SHEETVIEWS = '<sheetViews><sheetView tabSelected="1" workbookViewId="0"'.($this->rtl ? ' rightToLeft="1"' : '').'>';
|
||||
$SHEETVIEWS = '';
|
||||
$PANE = '';
|
||||
if (count($this->sheets[$idx]['rows'])) {
|
||||
if ($this->sheets[$idx]['frozen'] !== '' || isset($this->sheets[$idx]['frozen'][0]) || isset($this->sheets[$idx]['frozen'][1])) {
|
||||
// $AC = 'A1'; // Active Cell
|
||||
$x = $y = 0;
|
||||
if (is_string($this->sheets[$idx]['frozen'])) {
|
||||
if (is_string($this->sheets[$idx]['frozen'])) { //A1 format -> store ($AC) and convert to R1C1 0-based ($y/$x)
|
||||
$AC = $this->sheets[$idx]['frozen'];
|
||||
self::cell2coord($AC, $x, $y);
|
||||
} else {
|
||||
if (isset($this->sheets[$idx]['frozen'][0])) {
|
||||
$x = $this->sheets[$idx]['frozen'][0];
|
||||
}
|
||||
if (isset($this->sheets[$idx]['frozen'][1])) {
|
||||
$y = $this->sheets[$idx]['frozen'][1];
|
||||
}
|
||||
$AC = self::coord2cell($x, $y);
|
||||
[$y, $x] = self::cell2coord($AC);
|
||||
$y--;
|
||||
$x--;
|
||||
} else { //R1C1 1-based format -> store in R1C1 0-based ($y/$x) and convert to A1 format ($AC)
|
||||
$y = $this->sheets[$idx]['frozen'][0] - 1;
|
||||
$x = $this->sheets[$idx]['frozen'][1] - 1;
|
||||
$AC = self::coord2cell($this->sheets[$idx]['frozen']);
|
||||
}
|
||||
if ($x > 0 || $y > 0) {
|
||||
$split = '';
|
||||
|
@ -681,10 +787,9 @@ class SimpleXLSXGen
|
|||
}
|
||||
}
|
||||
if ($this->rtl || $PANE) {
|
||||
$SHEETVIEWS .= '<sheetViews>
|
||||
<sheetView workbookViewId="0"' . ($this->rtl ? ' rightToLeft="1"' : '');
|
||||
$SHEETVIEWS .= $PANE ? ">\r\n" . $PANE . "\r\n</sheetView>" : ' />';
|
||||
$SHEETVIEWS .= "\r\n</sheetViews>";
|
||||
$SHEETVIEWS .= '<sheetViews>' . self::NEWLINE . '<sheetView workbookViewId="0"' . ($this->rtl ? ' rightToLeft="1"' : '');
|
||||
$SHEETVIEWS .= $PANE ? ('>' . self::NEWLINE . $PANE . self::NEWLINE . '</sheetView>') : ' />';
|
||||
$SHEETVIEWS .= self::NEWLINE . '</sheetViews>';
|
||||
}
|
||||
$COLS[] = '<cols>';
|
||||
$CUR_ROW = 0;
|
||||
|
@ -701,7 +806,7 @@ class SimpleXLSXGen
|
|||
}
|
||||
$cname = $this->num2name($CUR_COL) . $CUR_ROW;
|
||||
if ($v === null || $v === '') {
|
||||
$row .= '<c r="' . $cname . '"/>';
|
||||
$row .= ' <c r="' . $cname . '"/>';
|
||||
continue;
|
||||
}
|
||||
$ct = $cv = $cf = null;
|
||||
|
@ -864,7 +969,7 @@ class SimpleXLSXGen
|
|||
}
|
||||
}
|
||||
if ($cv === null) {
|
||||
$v = $this->esc($v);
|
||||
$v = self::esc($v);
|
||||
if ($cf) {
|
||||
$ct = 'str';
|
||||
$cv = $v;
|
||||
|
@ -918,34 +1023,34 @@ class SimpleXLSXGen
|
|||
$this->XF[] = [$N, $A, $F, $FL, $C, $BG, $BR, $FS];
|
||||
}
|
||||
}
|
||||
$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";
|
||||
$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>' . self::NEWLINE;
|
||||
}
|
||||
$ROWS[] = '<row r="' . $CUR_ROW . '"' . ($RH ? ' customHeight="1" ht="' . $RH . '"' : '') . '>' . $row . "</row>";
|
||||
$ROWS[] = ' <row r="' . $CUR_ROW . '"' . ($RH ? ' customHeight="1" ht="' . $RH . '"' : '') . '>' . self::NEWLINE . $row . ' </row>';
|
||||
}
|
||||
foreach ($COL as $k => $max) {
|
||||
$w = isset($this->sheets[$idx]['colwidth'][$k]) ? $this->sheets[$idx]['colwidth'][$k] : min($max + 1, 60);
|
||||
$COLS[] = '<col min="' . $k . '" max="' . $k . '" width="' . $w . '" customWidth="1" />';
|
||||
$COLS[] = ' <col min="' . $k . '" max="' . $k . '" width="' . $w . '" customWidth="1" />';
|
||||
}
|
||||
$COLS[] = '</cols>';
|
||||
$REF = 'A1:' . $this->num2name(count($COL)) . $CUR_ROW;
|
||||
} else {
|
||||
$ROWS[] = '<row r="1"><c r="A1" t="s"><v>0</v></c></row>';
|
||||
$ROWS[] = '<row r="1"><c r="A1"></c></row>'; //'<row r="1"><c r="A1" t="s"><v>0</v></c></row>';
|
||||
$REF = 'A1:A1';
|
||||
}
|
||||
|
||||
$AUTOFILTER = '';
|
||||
if ($this->sheets[$idx]['autofilter']) {
|
||||
$AUTOFILTER = '<autoFilter ref="' . $this->sheets[$idx]['autofilter'] . '" />';
|
||||
$AUTOFILTER = '<autoFilter ref="' . $this->sheets[$idx]['autofilter'] . '"/>' . self::NEWLINE;
|
||||
}
|
||||
|
||||
$MERGECELLS = [];
|
||||
if (count($this->sheets[$idx]['mergecells'])) {
|
||||
$MERGECELLS[] = '';
|
||||
//$MERGECELLS[] = '';
|
||||
$MERGECELLS[] = '<mergeCells count="' . count($this->sheets[$idx]['mergecells']) . '">';
|
||||
foreach ($this->sheets[$idx]['mergecells'] as $m) {
|
||||
$MERGECELLS[] = '<mergeCell ref="' . $m . '"/>';
|
||||
$MERGECELLS[] = ' <mergeCell ref="' . $m . '"/>';
|
||||
}
|
||||
$MERGECELLS[] = '</mergeCells>';
|
||||
}
|
||||
|
@ -954,29 +1059,43 @@ class SimpleXLSXGen
|
|||
if (count($this->sheets[$idx]['hyperlinks'])) {
|
||||
$HYPERLINKS[] = '<hyperlinks>';
|
||||
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>';
|
||||
}
|
||||
|
||||
$COMMENTS = '';
|
||||
if (count($this->sheets[$idx]['comments'])) {
|
||||
$this->extLinkId += 2; //2 links: drawing in sheet?.xml and drawing and comments?.xml in rel file
|
||||
$this->sheets[$idx]['commentDrawingId'] = $this->extLinkId - 1;
|
||||
$COMMENTS = '<legacyDrawing r:id="rId' . $this->sheets[$idx]['commentDrawingId'] . '"/>' . self::NEWLINE;
|
||||
}
|
||||
|
||||
//restore locale
|
||||
setlocale(LC_NUMERIC, $_loc);
|
||||
|
||||
return str_replace(
|
||||
['{REF}', '{COLS}', '{ROWS}', '{AUTOFILTER}', '{MERGECELLS}', '{HYPERLINKS}', '{SHEETVIEWS}'],
|
||||
['{REF}', '{COLS}', '{ROWS}', '{AUTOFILTER}', '{MERGECELLS}', '{HYPERLINKS}', '{SHEETVIEWS}', '{COMMENTS}'],
|
||||
[
|
||||
$REF,
|
||||
implode("\r\n", $COLS),
|
||||
implode("\r\n", $ROWS),
|
||||
implode(self::NEWLINE, $COLS),
|
||||
implode(self::NEWLINE, $ROWS),
|
||||
$AUTOFILTER,
|
||||
implode("\r\n", $MERGECELLS),
|
||||
implode("\r\n", $HYPERLINKS),
|
||||
$SHEETVIEWS
|
||||
implode(self::NEWLINE, $MERGECELLS),
|
||||
implode(self::NEWLINE, $HYPERLINKS),
|
||||
$SHEETVIEWS,
|
||||
$COMMENTS
|
||||
],
|
||||
$template
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert column number (1, 2, ...) to column letter (A, B, ..., Z, AA, ...)
|
||||
*
|
||||
* @param integer $num Column number
|
||||
* @return string Column letter(s)
|
||||
*/
|
||||
public function num2name($num)
|
||||
{
|
||||
$numeric = ($num - 1) % 26;
|
||||
|
@ -995,9 +1114,9 @@ class SimpleXLSXGen
|
|||
return $excelTime;
|
||||
}
|
||||
// self::CALENDAR_WINDOWS_1900
|
||||
$excel1900isLeapYear = True;
|
||||
$excel1900isLeapYear = true;
|
||||
if (($year === 1900) && ($month <= 2)) {
|
||||
$excel1900isLeapYear = False;
|
||||
$excel1900isLeapYear = false;
|
||||
}
|
||||
$myExcelBaseDate = 2415020;
|
||||
// Julian base date Adjustment
|
||||
|
@ -1019,13 +1138,11 @@ class SimpleXLSXGen
|
|||
$this->defaultFont = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDefaultFontSize($size)
|
||||
{
|
||||
$this->defaultFontSize = $size;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
|
@ -1066,7 +1183,6 @@ class SimpleXLSXGen
|
|||
$this->category = $category;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setApplication($application)
|
||||
{
|
||||
$this->application = $application;
|
||||
|
@ -1077,35 +1193,65 @@ class SimpleXLSXGen
|
|||
$this->lastModifiedBy = $lastModifiedBy;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function autoFilter($range)
|
||||
{
|
||||
$this->sheets[$this->curSheet]['autofilter'] = $range;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function mergeCells($range)
|
||||
{
|
||||
$this->sheets[$this->curSheet]['mergecells'][] = $range;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setColWidth($col, $width)
|
||||
{
|
||||
$this->sheets[$this->curSheet]['colwidth'][$col] = $width;
|
||||
return $this;
|
||||
}
|
||||
public function rightToLeft($value = true)
|
||||
{
|
||||
$this->rtl = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function esc($str)
|
||||
public function autoFilter($range)
|
||||
{
|
||||
// XML UTF-8: #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
|
||||
// but we use fast version
|
||||
return str_replace(['&', '<', '>', "\x00", "\x03", "\x0B"], ['&', '<', '>', '', '', ''], $str);
|
||||
$this->sheets[$this->curSheet]['autofilter'] = $range;
|
||||
return $this;
|
||||
}
|
||||
public function mergeCells($range)
|
||||
{
|
||||
$this->sheets[$this->curSheet]['mergecells'][] = $range;
|
||||
return $this;
|
||||
}
|
||||
public function setColWidth($col, $width)
|
||||
{
|
||||
$this->sheets[$this->curSheet]['colwidth'][$col] = $width;
|
||||
return $this;
|
||||
}
|
||||
public function freezePanes($cell)
|
||||
{
|
||||
$this->sheets[$this->curSheet]['frozen'] = $cell;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* 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 = '')
|
||||
{
|
||||
if (is_array($cell)) {
|
||||
$cell = self::coord2cell($cell);
|
||||
} 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)
|
||||
|
@ -1122,19 +1268,32 @@ class SimpleXLSXGen
|
|||
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"], ['&', '<', '>', '', '', ''], $str);
|
||||
}
|
||||
|
||||
public static function raw($value)
|
||||
{
|
||||
return "\0" . $value;
|
||||
}
|
||||
|
||||
public static function cell2coord($cell, &$x, &$y)
|
||||
/**
|
||||
* Convert A1 cell reference format to R1C1 cell reference format (row/col number starting from 1)
|
||||
*
|
||||
* @param string $cell Cell reference in A1 format
|
||||
*
|
||||
* @return array Cell reference in R1C1 format as a 2-element integer array: [row (ie. y coord), col (ie. x coord)]
|
||||
*/
|
||||
public static function cell2coord($cell)
|
||||
{
|
||||
$x = $y = 0;
|
||||
$lettercount = 0;
|
||||
$cell = str_replace([' ', '\t', '\r', '\n', '\v', '\0'], '', $cell);
|
||||
$cell = str_replace([' ', '\t', '\r', '\n', '\v', '\0', '$'], '', $cell);
|
||||
if (empty($cell)) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
$cell = strtoupper($cell);
|
||||
for ($i = 0, $len = strlen($cell); $i < $len; $i++) {
|
||||
|
@ -1149,24 +1308,33 @@ class SimpleXLSXGen
|
|||
$x += (ord($cell[$i]) - ord('A') + 1) * (26 ** $e);
|
||||
$e++;
|
||||
}
|
||||
$x++; //to make 1-based
|
||||
}
|
||||
if ($lettercount < strlen($cell)) {
|
||||
$y = ((int)substr($cell, $lettercount)) - 1;
|
||||
$y = ((int)substr($cell, $lettercount));
|
||||
}
|
||||
return [$y, $x];
|
||||
}
|
||||
|
||||
public static function coord2cell($x, $y)
|
||||
/**
|
||||
* Convert R1C1 cell reference format (row/col 1-based) to A1 cell reference format
|
||||
*
|
||||
* @param integer $y Row number (starting from 1) or a 2-element integer array with cell reference.
|
||||
* @param integer $x Optional. Column number (starting from 1). Not used if $y is an array.
|
||||
*
|
||||
* @return string Cell reference in A1 format
|
||||
*/
|
||||
public static function coord2cell($y, $x = null)
|
||||
{
|
||||
if (is_array($y)) {
|
||||
$x = $y[1];
|
||||
$y = $y[0];
|
||||
}
|
||||
$c = '';
|
||||
for ($i = $x; $i >= 0; $i = ((int)($i / 26)) - 1) {
|
||||
for ($i = $x - 1; $i >= 0; $i = ((int)($i / 26)) - 1) {
|
||||
$c = chr(ord('A') + $i % 26) . $c;
|
||||
}
|
||||
return $c . ($y + 1);
|
||||
return $c . strval($y);
|
||||
}
|
||||
|
||||
public function freezePanes($cell)
|
||||
{
|
||||
$this->sheets[$this->curSheet]['frozen'] = $cell;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
<?php
|
||||
|
||||
/** @noinspection ReturnTypeCanBeDeclaredInspection */
|
||||
/** @noinspection PhpMissingReturnTypeInspection */
|
||||
/** @noinspection NullCoalescingOperatorCanBeUsedInspection */
|
||||
/** @noinspection PhpIssetCanBeReplacedWithCoalesceInspection */
|
||||
|
||||
namespace Shuchkin;
|
||||
|
||||
/**
|
||||
* Class SimpleXLSXGenEx
|
||||
*
|
||||
* Helper methods to manage sheet data. It needs SimpleXLSXGen class.
|
||||
*
|
||||
* @author Sergey Shuchkin <sergey.shuchkin@gmail.com>
|
||||
*/
|
||||
class SimpleXLSXGenEx
|
||||
{
|
||||
const EDGE_LEFT = 1;
|
||||
const EDGE_RIGHT = 2;
|
||||
const EDGE_TOP = 4;
|
||||
const EDGE_BOTTOM = 8;
|
||||
const EDGE_ALL = self::EDGE_LEFT + self::EDGE_RIGHT + self::EDGE_TOP + self::EDGE_BOTTOM;
|
||||
|
||||
/**
|
||||
* Convert A1 cell reference format to R1C1 cell reference format (row/col number starting from 1)
|
||||
*
|
||||
* @param string $cell Cell reference in A1 format
|
||||
*
|
||||
* @return array Cell reference in R1C1 format as a 2-element integer array: [row (ie. y coord), col (ie. x coord)]
|
||||
*/
|
||||
public static function cell2coord($cell)
|
||||
{
|
||||
$x = $y = 0;
|
||||
$lettercount = 0;
|
||||
$cell = str_replace([' ', '\t', '\r', '\n', '\v', '\0', '$'], '', $cell);
|
||||
if (empty($cell)) {
|
||||
return [];
|
||||
}
|
||||
$cell = strtoupper($cell);
|
||||
for ($i = 0, $len = strlen($cell); $i < $len; $i++) {
|
||||
if ($cell[$i] >= 'A' && $cell[$i] <= 'Z') {
|
||||
$lettercount++;
|
||||
}
|
||||
}
|
||||
if ($lettercount > 0) {
|
||||
$x = ord($cell[$lettercount - 1]) - ord('A');
|
||||
$e = 1;
|
||||
for ($i = $lettercount - 2; $i >= 0; $i--) {
|
||||
$x += (ord($cell[$i]) - ord('A') + 1) * (26 ** $e);
|
||||
$e++;
|
||||
}
|
||||
$x++; //to make 1-based
|
||||
}
|
||||
if ($lettercount < strlen($cell)) {
|
||||
$y = ((int)substr($cell, $lettercount));
|
||||
}
|
||||
return [$y, $x];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert R1C1 cell reference format (row/col 1-based) to A1 cell reference format
|
||||
*
|
||||
* @param integer $y Row number (starting from 1) or a 2-element integer array with cell reference.
|
||||
* @param integer $x Optional. Column number (starting from 1). Not used if $y is an array.
|
||||
*
|
||||
* @return string Cell reference in A1 format
|
||||
*/
|
||||
public static function coord2cell($y, $x = null)
|
||||
{
|
||||
if (is_array($y)) {
|
||||
$x = $y[1];
|
||||
$y = $y[0];
|
||||
}
|
||||
$c = '';
|
||||
for ($i = $x - 1; $i >= 0; $i = ((int)($i / 26)) - 1) {
|
||||
$c = chr(ord('A') + $i % 26) . $c;
|
||||
}
|
||||
return $c . strval($y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert A1 range reference format to R1C1 range reference format
|
||||
*
|
||||
* @param string $range Range reference in A1 format
|
||||
*
|
||||
* @return array Range reference in R1C1 format as a 4-element integer array: [top-left row, top-left col, bottom-right row, bottom-right col]
|
||||
*/
|
||||
public static function range2coord($range)
|
||||
{
|
||||
$temp = explode(':', $range);
|
||||
if (empty($temp[1])) return self::cell2coord($temp[0]);
|
||||
return array_merge(self::cell2coord($temp[0]), self::cell2coord($temp[1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert R1C1 range reference format (row/col 1-based) to A1 range reference format
|
||||
*
|
||||
* Possible parameters:
|
||||
* top-left row number, top-left column number, bottom-right row number, bottom-right column number
|
||||
* [top-left row number, top-left column number], [bottom-right row number, bottom-right column number]
|
||||
* [top-left row number, top-left column number, bottom-right row number, bottom-right column number]
|
||||
*
|
||||
* @return string Range reference in A1 format
|
||||
*/
|
||||
public static function coord2range($p1, ...$p)
|
||||
{
|
||||
switch (count($p)) {
|
||||
case 0: $yi = $p1[0]; $xi = $p1[1]; $yf = $p1[2]; $xf = $p1[3]; break;
|
||||
case 1: $yi = $p1[0]; $xi = $p1[1]; $yf = $p[0][0]; $xf = $p[0][1]; break;
|
||||
default: $yi = $p1; $xi = $p[0]; $yf = $p[1]; $xf = $p[2]; break;
|
||||
}
|
||||
return self::coord2cell($yi, $xi) . ':' . self::coord2cell($yf, $xf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change or add cell style parameters without losing the original content or previously established styles
|
||||
*
|
||||
* @param string $data Data matrix (2-dim 0-based array) where style will be modified. Passed by reference.
|
||||
* @param string|array $cell Cell to modify in A1 format (string) or R1C1 format (2 integer elements array)
|
||||
* @param array $style Associative array with style attributes name/value pairs to change/add. You can use wildcard for
|
||||
* specific borders to retain original value.
|
||||
* @param integer $edge Optional. If border attribute specified in $style, this parameter indicates on which edge is applied.
|
||||
* The unspecified edges retain their original value (or 'none' if not specified).
|
||||
* Default: all edges. You can combine (adding or or-ing) the constants EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM, EDGE_LEFT
|
||||
* @return void
|
||||
*/
|
||||
public static function setCellStyle(&$data, $cell, $style, $edge = self::EDGE_ALL)
|
||||
{
|
||||
if (is_string($cell)) $cell = self::cell2coord($cell);//A1 to R1C1 if needed
|
||||
if (!array_key_exists($cell[0] - 1, $data) || !array_key_exists($cell[1] - 1, $data[$cell[0] - 1])) return;//quit if cell doesn't exist
|
||||
//border processing
|
||||
if (array_key_exists('border', $style)) {
|
||||
//get original border as 4 element array
|
||||
$oldBorder = self::getTagAttributes($data[$cell[0] - 1][$cell[1] - 1], 'style', 'border');
|
||||
if (empty($oldBorder)) {
|
||||
$oldBorder = ['none', 'none', 'none', 'none']; //if border not present in original cell, create a no-border attribute
|
||||
} else {
|
||||
$oldBorder = explode(' ', $oldBorder);
|
||||
if (count($oldBorder) == 1) $oldBorder = [$oldBorder[0], $oldBorder[0], $oldBorder[0], $oldBorder[0]];
|
||||
}
|
||||
//get desired border as 4 element array
|
||||
$border = explode(' ', $style['border']);
|
||||
if (count($border) == 1) $border = [$border[0], $border[0], $border[0], $border[0]];
|
||||
//new border copied from old one
|
||||
$newBorder = $oldBorder;
|
||||
//set border as indicated by $edge, except when $border has wildcard
|
||||
if ($edge & self::EDGE_TOP && $border[0] != '*') $newBorder[0] = $border[0];
|
||||
if ($edge & self::EDGE_RIGHT && $border[1] != '*') $newBorder[1] = $border[1];
|
||||
if ($edge & self::EDGE_BOTTOM && $border[2] != '*') $newBorder[2] = $border[2];
|
||||
if ($edge & self::EDGE_LEFT && $border[3] != '*') $newBorder[3] = $border[3];
|
||||
//implode to the new border
|
||||
$style['border'] = implode(' ', $newBorder);
|
||||
}
|
||||
$data[$cell[0] - 1][$cell[1] - 1] = self::setTagAttributes($data[$cell[0] - 1][$cell[1] - 1], 'style', $style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply style to a sheet range. Border style are only applied to edges of range.
|
||||
*
|
||||
* @param array $data Data matrix (2-dim 0-based array) where style will be modified. Passed by reference.
|
||||
* @param string|array $range Range to modify in A1 format (string) or R1C1 format (4-element integer array)
|
||||
* @param array $style Associative array with style attributes name/value pairs to change
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setRangeStyle(&$data, $range, $style)
|
||||
{
|
||||
if (is_string($range)) $range = self::range2coord($range);
|
||||
// 1 cell range
|
||||
if (!isset($range[2])) return self::setCellStyle($data, $range, $style); //1 cell case
|
||||
// Edge of range
|
||||
self::setCellStyle($data, [$range[0], $range[1]], $style, self::EDGE_TOP + self::EDGE_LEFT); //top-left corner
|
||||
for ($i = $range[1] + 1; $i < $range[3]; $i++) self::setCellStyle($data, [$range[0], $i], $style, self::EDGE_TOP); //loop top edge (except first and last column)
|
||||
self::setCellStyle($data, [$range[0], $range[3]], $style, self::EDGE_TOP + self::EDGE_RIGHT); //top-right corner
|
||||
for ($i = $range[0] + 1; $i < $range[2]; $i++) self::setCellStyle($data, [$i, $range[3]], $style, self::EDGE_RIGHT); //loop right edge (except top and bottom row)
|
||||
self::setCellStyle($data, [$range[2], $range[3]], $style, self::EDGE_BOTTOM + self::EDGE_RIGHT); //bottom-right corner
|
||||
for ($i = $range[3] - 1; $i > $range[1]; $i--) self::setCellStyle($data, [$range[2], $i], $style, self::EDGE_BOTTOM); //loop bottom edge (except right and left column)
|
||||
self::setCellStyle($data, [$range[2], $range[1]], $style, self::EDGE_BOTTOM + self::EDGE_LEFT); //bottom-left corner
|
||||
for ($i = $range[2] - 1; $i > $range[0]; $i--) self::setCellStyle($data, [$i, $range[1]], $style, self::EDGE_LEFT); //loop left edge (except bottom and top row)
|
||||
// Internal cells
|
||||
if (array_key_exists('border', $style)) unset($style['border']);
|
||||
for ($i = $range[0] + 1; $i < $range[2]; $i++) {
|
||||
for ($j = $range[1] + 1; $j < $range[3]; $j++) {
|
||||
self::setCellStyle($data, [$i, $j], $style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tag attributes from a HTML-style tag as an associative array with attributes name/value pairs.
|
||||
* If $attribute specified, get value of the specified attribute.
|
||||
*
|
||||
* @param string $str Text to extract tag attributes
|
||||
* @param string $tag The tag to look for
|
||||
* @param string $attribute Optional. Get the value of specified attribute. Default: get all attributes name/value as an array.
|
||||
*
|
||||
* @return array|string Attributes name/value as an array ($attribute == '') or attribute value as a string ($attribute != '').
|
||||
*/
|
||||
public static function getTagAttributes($str, $tag, $attribute = '')
|
||||
{
|
||||
$attributes = ($attribute === '') ? [] : '';
|
||||
if (preg_match("/<{$tag}\s+([^>]+)(?:>)/i", $str, $m)) {
|
||||
$tagcontent = $m[1];
|
||||
if (preg_match_all('/([a-z0-9\-_]+)=(["\'])(.*?)\2/si', $tagcontent, $m, PREG_SET_ORDER)) {
|
||||
foreach ($m as $match) {
|
||||
if ($attribute !== '') {
|
||||
if ($attribute === $match[1]) return $match[3];
|
||||
} else {
|
||||
$attributes[$match[1]] = $match[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change HTML-style tag attributes. If tag doesn't exist, is created surrounding the previous content.
|
||||
*
|
||||
* @param string $str Text to modify
|
||||
* @param string $tag Tag in text to modify
|
||||
* @param array $attributes Associative array with attribute name/value pairs
|
||||
*
|
||||
* @return string Modified text
|
||||
*/
|
||||
public static function setTagAttributes($str, $tag, $attributes)
|
||||
{
|
||||
if (empty($attributes)) return $str;
|
||||
$opening = "<{$tag}";
|
||||
if (preg_match("/(<{$tag}\s*[^>]*)(>.*<\/{$tag}>)/i", $str, $m)) {
|
||||
$stropen = $m[1];
|
||||
} else {
|
||||
$str = "<{$tag}>{$str}</{$tag}>";
|
||||
$stropen = $opening;
|
||||
}
|
||||
//merge original attributes and specified attributes (replacing if necessary)
|
||||
$attributes = array_merge(self::getTagAttributes($str, $tag), $attributes);
|
||||
//add attributes to tag
|
||||
foreach ($attributes as $attr => $value) $opening .= " {$attr}=\"{$value}\"";
|
||||
//replace original tag (with attributes) with the new one
|
||||
$str = substr_replace($str, $opening, strpos($str, $stropen), strlen($stropen));
|
||||
return $str;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue