GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 610aa0...6cebac )
by Denis
02:47
created

ExcelWriter::buildWorkbookXML()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 24
rs 8.9713
cc 2
eloc 19
nc 2
nop 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A ExcelWriter::xlsCell() 0 9 2
1
<?php
2
namespace Ellumilel;
3
4
use Ellumilel\DocProps\App;
5
use Ellumilel\DocProps\Core;
6
use Ellumilel\Rels\Relationships;
7
use Ellumilel\Xl\SharedStrings;
8
use Ellumilel\Xl\Workbook;
9
10
/**
11
 * Class ExcelWriter
12
 * @package Ellumilel
13
 * @author Denis Tikhonov <[email protected]>
14
 */
15
class ExcelWriter
16
{
17
    /**
18
     * @link http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP010073849.aspx
19
     */
20
    const EXCEL_MAX_ROW = 1048576;
21
    const EXCEL_MAX_RANGE = 2147483647;
22
    const EXCEL_MAX_COL = 16384;
23
24
    /** @var string */
25
    private $urlSchemaFormat = 'http://schemas.openxmlformats.org/officeDocument/2006';
26
27
    /** @var string */
28
    protected $author ='Unknown Author';
29
    /** @var array */
30
    protected $sheets = [];
31
    /** @var array */
32
    protected $sharedStrings = [];//unique set
33
    /** @var int */
34
    protected $sharedStringCount = 0;//count of non-unique references to the unique set
35
    /** @var array */
36
    protected $tempFiles = [];
37
    /** @var array */
38
    protected $cellFormats = [];//contains excel format like YYYY-MM-DD HH:MM:SS
39
    /** @var array */
40
    protected $cellTypes = [];//contains friendly format like datetime
41
    /** @var string  */
42
    protected $currentSheet = '';
43
    /** @var null */
44
    protected $tmpDir = null;
45
    /** @var Core */
46
    protected $core;
47
    /** @var App */
48
    protected $app;
49
    /** @var Workbook */
50
    protected $workbook;
51
52
    /**
53
     * ExcelWriter constructor.
54
     * @throws \Exception
55
     */
56
    public function __construct()
57
    {
58
        if (!class_exists('ZipArchive')) {
59
            throw new \Exception('ZipArchive not found');
60
        }
61
62
        if (!ini_get('date.timezone')) {
63
            //using date functions can kick out warning if this isn't set
64
            date_default_timezone_set('UTC');
65
        }
66
        $this->addCellFormat($cell_format = 'GENERAL');
67
        $this->core = new Core();
68
        $this->app = new App();
69
        $this->workbook = new Workbook();
70
    }
71
72
    /**
73
     * @param string $author
74
     */
75
    public function setAuthor($author = '')
76
    {
77
        $this->author = $author;
78
    }
79
80
    public function __destruct()
81
    {
82
        if (!empty($this->tempFiles)) {
83
            foreach ($this->tempFiles as $tempFile) {
84
                if (file_exists($tempFile)) {
85
                    unlink($tempFile);
86
                }
87
            }
88
        }
89
    }
90
91
    /**
92
     * @param $dir
93
     */
94
    public function setTmpDir($dir)
95
    {
96
        $this->tmpDir = $dir;
97
    }
98
99
    /**
100
     * Return tmpFileName
101
     * @return string
102
     */
103
    protected function tempFilename()
104
    {
105
        $tmpDir = is_null($this->tmpDir) ? sys_get_temp_dir() : $this->tmpDir;
106
        $filename = tempnam($tmpDir, "exlsWriter_");
107
        $this->tempFiles[] = $filename;
108
109
        return $filename;
110
    }
111
112
    public function writeToStdOut()
113
    {
114
        $tempFile = $this->tempFilename();
115
        $this->writeToFile($tempFile);
116
        readfile($tempFile);
117
    }
118
119
    /**
120
     * @return string
121
     */
122
    public function writeToString()
123
    {
124
        $tempFile = $this->tempFilename();
125
        $this->writeToFile($tempFile);
126
        $string = file_get_contents($tempFile);
127
128
        return $string;
129
    }
130
131
    /**
132
     * @param string $filename
133
     */
134
    public function writeToFile($filename)
135
    {
136
        foreach ($this->sheets as $sheetName => $sheet) {
137
            $this->finalizeSheet($sheetName);
138
        }
139
        if (file_exists($filename) && is_writable($filename)) {
140
            unlink($filename);
141
        }
142
143
        $zip = new \ZipArchive();
144
        if (empty($this->sheets) || !$zip->open($filename, \ZipArchive::CREATE)) {
145
            self::log("Error in ".__CLASS__."::".__FUNCTION__.", no worksheets defined or unable to create zip.");
146
            return;
147
        }
148
149
        $this->workbook->setSheet($this->sheets);
150
151
        $contentTypes = new ContentTypes(!empty($this->sharedStrings));
152
        $contentTypes->setSheet($this->sheets);
153
154
        $rels = new Relationships(!empty($this->sharedStrings));
155
        $rels->setSheet($this->sheets);
156
157
        $zip->addEmptyDir("docProps/");
158
        $zip->addFromString("docProps/app.xml", $this->app->buildAppXML());
159
        $zip->addFromString("docProps/core.xml", $this->core->buildCoreXML());
160
        $zip->addEmptyDir("_rels/");
161
        $zip->addFromString("_rels/.rels", $rels->buildRelationshipsXML());
162
        $zip->addEmptyDir("xl/worksheets/");
163
        foreach ($this->sheets as $sheet) {
164
            /** @var Sheet $sheet */
165
            $zip->addFile($sheet->getFilename(), "xl/worksheets/".$sheet->getXmlName());
166
        }
167
        if (!empty($this->sharedStrings)) {
168
            $zip->addFile(
169
                $this->writeSharedStringsXML(),
170
                "xl/sharedStrings.xml"
171
            );
172
        }
173
        $zip->addFromString("xl/workbook.xml", $this->workbook->buildWorkbookXML());
174
        $zip->addFile(
175
            $this->writeStylesXML(),
176
            "xl/styles.xml"
177
        );
178
        $zip->addFromString("[Content_Types].xml", $contentTypes->buildContentTypesXML());
179
        $zip->addEmptyDir("xl/_rels/");
180
        $zip->addFromString("xl/_rels/workbook.xml.rels", $rels->buildWorkbookRelationshipsXML());
181
        $zip->close();
182
    }
183
184
    /**
185
     * @param string $sheetName
186
     */
187
    protected function initializeSheet($sheetName)
188
    {
189
        if ($this->currentSheet == $sheetName || isset($this->sheets[$sheetName])) {
190
            return;
191
        }
192
        $sheetFilename = $this->tempFilename();
193
        $sheetXmlName = 'sheet' . (count($this->sheets) + 1).".xml";
194
        $sheetObj = new Sheet();
195
        $sheetObj
196
            ->setFilename($sheetFilename)
197
            ->setSheetName($sheetName)
198
            ->setXmlName($sheetXmlName)
199
            ->setWriter(new Writer($sheetFilename))
200
        ;
201
        $this->sheets[$sheetName] = $sheetObj;
202
        /** @var Sheet $sheet */
203
        $sheet = &$this->sheets[$sheetName];
204
        $selectedTab = count($this->sheets) == 1 ? 'true' : 'false';//only first sheet is selected
205
        $maxCell = ExcelWriter::xlsCell(self::EXCEL_MAX_ROW, self::EXCEL_MAX_COL);//XFE1048577
206
        $sheet->getWriter()->write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
207
        $sheet->getWriter()->write(
208
            '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" 
209
                xmlns:r="'.$this->urlSchemaFormat.'/relationships">'
210
        );
211
        $sheet->getWriter()->write('<sheetPr filterMode="false">');
212
        $sheet->getWriter()->write('<pageSetUpPr fitToPage="false"/>');
213
        $sheet->getWriter()->write('</sheetPr>');
214
        $sheet->setMaxCellTagStart($sheet->getWriter()->fTell());
215
        $sheet->getWriter()->write('<dimension ref="A1:'.$maxCell.'"/>');
216
        $sheet->setMaxCellTagEnd($sheet->getWriter()->fTell());
217
        $sheet->getWriter()->write('<sheetViews>');
218
        $sheet->getWriter()->write(
219
            '<sheetView colorId="64" defaultGridColor="true" rightToLeft="false" showFormulas="false" 
220
            showGridLines="true" showOutlineSymbols="true" showRowColHeaders="true" showZeros="true" 
221
            tabSelected="'.$selectedTab.'" topLeftCell="A1" view="normal" windowProtection="false" 
222
            workbookViewId="0" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100">'
223
        );
224
        $sheet->getWriter()->write('<selection activeCell="A1" activeCellId="0" pane="topLeft" sqref="A1"/>');
225
        $sheet->getWriter()->write('</sheetView>');
226
        $sheet->getWriter()->write('</sheetViews>');
227
        $sheet->getWriter()->write('<cols>');
228
        $sheet->getWriter()->write('<col collapsed="false" hidden="false" max="1025" min="1" style="0" width="11.5"/>');
229
        $sheet->getWriter()->write('</cols>');
230
        $sheet->getWriter()->write('<sheetData>');
231
    }
232
233
    /**
234
     * @param $cellFormat
235
     *
236
     * @return string
237
     */
238
    private function determineCellType($cellFormat)
239
    {
240
        $cellFormat = str_replace("[RED]", "", $cellFormat);
241
        if ($cellFormat == 'GENERAL') {
242
            return 'string';
243
        }
244
        if ($cellFormat == '0') {
245
            return 'numeric';
246
        }
247
        if (preg_match("/[H]{1,2}:[M]{1,2}/", $cellFormat)) {
248
            return 'datetime';
249
        }
250
        if (preg_match("/[M]{1,2}:[S]{1,2}/", $cellFormat)) {
251
            return 'datetime';
252
        }
253
        if (preg_match("/[YY]{2,4}/", $cellFormat)) {
254
            return 'date';
255
        }
256
        if (preg_match("/[D]{1,2}/", $cellFormat)) {
257
            return 'date';
258
        }
259
        if (preg_match("/[M]{1,2}/", $cellFormat)) {
260
            return 'date';
261
        }
262
        if (preg_match("/$/", $cellFormat)) {
263
            return 'currency';
264
        }
265
        if (preg_match("/%/", $cellFormat)) {
266
            return 'percent';
267
        }
268
        if (preg_match("/0/", $cellFormat)) {
269
            return 'numeric';
270
        }
271
272
        return 'string';
273
    }
274
275
    /**
276
     * @todo  check escaping
277
     *
278
     * @param $cellFormat
279
     *
280
     * @return string
281
     */
282
    private function escapeCellFormat($cellFormat)
283
    {
284
        $ignoreUntil = '';
285
        $escaped = '';
286
        for ($i = 0, $ix = strlen($cellFormat); $i < $ix; $i++) {
287
            $c = $cellFormat[$i];
288
            if ($ignoreUntil == '' && $c == '[') {
289
                $ignoreUntil = ']';
290
            } else {
291
                if ($ignoreUntil == '' && $c == '"') {
292
                    $ignoreUntil = '"';
293
                } else {
294
                    if ($ignoreUntil == $c) {
295
                        $ignoreUntil = '';
296
                    }
297
                }
298
            }
299
            if ($ignoreUntil == '' &&
300
                ($c == ' ' || $c == '-' || $c == '(' || $c == ')') &&
301
                ($i == 0 || $cellFormat[$i - 1] != '_')
302
            ) {
303
                $escaped .= "\\".$c;
304
            } else {
305
                $escaped .= $c;
306
            }
307
        }
308
309
        return $escaped;
310
    }
311
312
    /**
313
     * backwards compatibility
314
     *
315
     * @param $cellFormat
316
     *
317
     * @return int|mixed
318
     */
319
    private function addCellFormat($cellFormat)
320
    {
321
        switch ($cellFormat) {
322
            case 'string':
323
                $cellFormat = 'GENERAL';
324
                break;
325
            case 'integer':
326
                $cellFormat = '0';
327
                break;
328
            case 'date':
329
                $cellFormat = 'YYYY-MM-DD';
330
                break;
331
            case 'datetime':
332
                $cellFormat = 'YYYY-MM-DD HH:MM:SS';
333
                break;
334
            case 'dollar':
335
                $cellFormat = '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00';
336
                break;
337
            case 'money':
338
                $cellFormat = '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00';
339
                break;
340
            case 'euro':
341
                $cellFormat = '#,##0.00 [$€-407];[RED]-#,##0.00 [$€-407]';
342
                break;
343
            case 'NN':
344
                $cellFormat = 'DDD';
345
                break;
346
            case 'NNN':
347
                $cellFormat = 'DDDD';
348
                break;
349
            case 'NNNN':
350
                $cellFormat = 'DDDD", "';
351
                break;
352
        }
353
354
        $cellFormat = strtoupper($cellFormat);
355
        $position = array_search($cellFormat, $this->cellFormats, $strict = true);
356
        if ($position === false) {
357
            $position = count($this->cellFormats);
358
            $this->cellFormats[] = $this->escapeCellFormat($cellFormat);
359
            $this->cellTypes[] = $this->determineCellType($cellFormat);
360
        }
361
362
        return $position;
363
    }
364
365
    /**
366
     * @param string $sheetName
367
     * @param array $headerTypes
368
     * @param bool $suppressRow
369
     */
370
    public function writeSheetHeader($sheetName, array $headerTypes, $suppressRow = false)
371
    {
372
        if (empty($sheetName) || empty($headerTypes) || !empty($this->sheets[$sheetName])) {
373
            return;
374
        }
375
        $this->initializeSheet($sheetName);
376
        /** @var Sheet $sheet */
377
        $sheet = &$this->sheets[$sheetName];
378
        $sheet->setColumns([]);
379
        foreach ($headerTypes as $val) {
380
            $sheet->setColumn($this->addCellFormat($val));
381
        }
382
        if (!$suppressRow) {
383
            $this->writeRowHeader($sheet, array_keys($headerTypes));
384
            $sheet->increaseRowCount();
385
        }
386
        $this->currentSheet = $sheetName;
387
    }
388
389
    /**
390
     * @param Sheet $sheet
391
     * @param array $headerRow
392
     */
393
    private function writeRowHeader(Sheet $sheet, $headerRow)
394
    {
395
        $sheet->getWriter()->write(
396
            '<row collapsed="false" customFormat="false" 
397
                customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="'.(1).'">'
398
        );
399
        foreach ($headerRow as $k => $v) {
400
            $this->writeCell($sheet->getWriter(), 0, $k, $v, $cell_format_index = '0');
401
        }
402
        $sheet->getWriter()->write('</row>');
403
    }
404
405
    /**
406
     * @param string $sheetName
407
     * @param array $row
408
     */
409
    public function writeSheetRow($sheetName, array $row)
410
    {
411
        if (empty($sheetName) || empty($row)) {
412
            return;
413
        }
414
        $this->initializeSheet($sheetName);
415
        /** @var Sheet $sheet */
416
        $sheet = &$this->sheets[$sheetName];
417
        $columns = $sheet->getColumns();
418
        if (empty($columns)) {
419
            $sheet->setColumns(array_fill($from = 0, $until = count($row), '0'));
420
        }
421
        $sheet->getWriter()->write(
422
            '<row collapsed="false" customFormat="false" customHeight="false" 
423
            hidden="false" ht="12.1" outlineLevel="0" r="'.($sheet->getRowCount() + 1).'">'
424
        );
425
        $column_count = 0;
426
        $sheetColumns = $sheet->getColumns();
427
        foreach ($row as $k => $v) {
428
            $this->writeCell(
429
                $sheet->getWriter(),
430
                $sheet->getRowCount(),
431
                $column_count,
432
                $v,
433
                $sheetColumns[$column_count]
434
            );
435
            $column_count++;
436
        }
437
        $sheet->getWriter()->write('</row>');
438
        $sheet->increaseRowCount();
439
        $this->currentSheet = $sheetName;
440
    }
441
442
    /**
443
     * @param string $sheetName
444
     */
445
    protected function finalizeSheet($sheetName)
446
    {
447
        if (empty($sheetName) || $this->sheets[$sheetName]->getFinalized()) {
448
            return;
449
        }
450
        /** @var Sheet $sheet */
451
        $sheet = &$this->sheets[$sheetName];
452
        $sheet->getWriter()->write('</sheetData>');
453
        $mergeCells = $sheet->getMergeCells();
454
        if (!empty($mergeCells)) {
455
            $sheet->getWriter()->write('<mergeCells>');
456
            foreach ($mergeCells as $range) {
457
                $sheet->getWriter()->write('<mergeCell ref="'.$range.'"/>');
458
            }
459
            $sheet->getWriter()->write('</mergeCells>');
460
        }
461
        $sheet->getWriter()->write(
462
            '<printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false"
463
                verticalCentered="false"/>'
464
        );
465
        $sheet->getWriter()->write(
466
            '<pageMargins left="0.5" right="0.5" top="1.0" bottom="1.0" header="0.5" footer="0.5"/>'
467
        );
468
        $sheet->getWriter()->write(
469
            '<pageSetup blackAndWhite="false" cellComments="none" copies="1" draft="false" firstPageNumber="1" 
470
                fitToHeight="1" fitToWidth="1" horizontalDpi="300" orientation="portrait" pageOrder="downThenOver" 
471
                paperSize="1" scale="100" useFirstPageNumber="true" usePrinterDefaults="false" verticalDpi="300"/>'
472
        );
473
        $sheet->getWriter()->write('<headerFooter differentFirst="false" differentOddEven="false">');
474
        $sheet->getWriter()->write(
475
            '<oddHeader>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12&amp;A</oddHeader>'
476
        );
477
        $sheet->getWriter()->write(
478
            '<oddFooter>&amp;C&amp;&quot;Times New Roman,Regular&quot;&amp;12Page &amp;P</oddFooter>'
479
        );
480
        $sheet->getWriter()->write('</headerFooter>');
481
        $sheet->getWriter()->write('</worksheet>');
482
        $maxCell = self::xlsCell($sheet->getRowCount() - 1, count($sheet->getColumns()) - 1);
483
        $maxCellTag = '<dimension ref="A1:'.$maxCell.'"/>';
484
        $paddingLength = $sheet->getMaxCellTagEnd() - $sheet->getMaxCellTagStart() - strlen($maxCellTag);
485
        $sheet->getWriter()->fSeek($sheet->getMaxCellTagStart());
486
        $sheet->getWriter()->write($maxCellTag.str_repeat(" ", $paddingLength));
487
        $sheet->getWriter()->close();
488
        $sheet->setFinalized(true);
489
    }
490
491
    /**
492
     * @param string $sheetName
493
     * @param int $startCellRow
494
     * @param int $startCellColumn
495
     * @param int $endCellRow
496
     * @param int $endCellColumn
497
     */
498
    public function markMergedCell($sheetName, $startCellRow, $startCellColumn, $endCellRow, $endCellColumn)
499
    {
500
        if (empty($sheetName) || $this->sheets[$sheetName]->getFinalized()) {
501
            return;
502
        }
503
        $this->initializeSheet($sheetName);
504
        /** @var Sheet $sheet */
505
        $sheet = &$this->sheets[$sheetName];
506
        $startCell = self::xlsCell($startCellRow, $startCellColumn);
507
        $endCell = self::xlsCell($endCellRow, $endCellColumn);
508
        $sheet->setMergeCells($startCell.":".$endCell);
509
    }
510
511
    /**
512
     * @param array $data
513
     * @param string $sheetName
514
     * @param array $headerTypes
515
     */
516
    public function writeSheet(array $data, $sheetName = '', array $headerTypes = [])
517
    {
518
        $sheetName = empty($sheetName) ? 'Sheet1' : $sheetName;
519
        $data = empty($data) ? [['']] : $data;
520
        if (!empty($headerTypes)) {
521
            $this->writeSheetHeader($sheetName, $headerTypes);
522
        }
523
        foreach ($data as $i => $row) {
524
            $this->writeSheetRow($sheetName, $row);
525
        }
526
        $this->finalizeSheet($sheetName);
527
    }
528
529
    /**
530
     * @param Writer $file
531
     * @param $rowNumber
532
     * @param $columnNumber
533
     * @param $value
534
     * @param $cellIndex
535
     */
536
    protected function writeCell(
537
        Writer $file,
538
        $rowNumber,
539
        $columnNumber,
540
        $value,
541
        $cellIndex
542
    ) {
543
        $cellType = $this->cellTypes[$cellIndex];
544
        $cellName = self::xlsCell($rowNumber, $columnNumber);
545
        if (!is_scalar($value) || $value === '') {
546
            $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'"/>');
547
        } elseif (is_string($value) && $value{0} == '=') {
548
            $file->write(
549
                sprintf('<c r="%s" s="%s" t="s"><f>%s</f></c>', $cellName, $cellIndex, self::xmlspecialchars($value))
550
            );
551
        } elseif ($cellType == 'date') {
552
            $file->write(
553
                sprintf('<c r="%s" s="%s" t="n"><v>%s</v></c>', $cellName, $cellIndex, self::convertDateTime($value))
554
            );
555
        } elseif ($cellType == 'datetime') {
556
            $file->write(
557
                '<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.self::convertDateTime($value).'</v></c>'
558
            );
559
        } elseif ($cellType == 'currency' || $cellType == 'percent' || $cellType == 'numeric') {
560
            $file->write(
561
                '<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.self::xmlspecialchars($value).'</v></c>'
562
            );
563
        } else {
564
            if (!is_string($value)) {
565
                $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.($value * 1).'</v></c>');
566
            } else {
567
                if ($value{0} != '0' && $value{0} != '+' && filter_var(
568
                    $value,
569
                    FILTER_VALIDATE_INT,
570
                    ['options' => ['max_range' => self::EXCEL_MAX_RANGE]]
571
                )) {
572
                    $file->write('<c r="'.$cellName.'" s="'.$cellIndex.'" t="n"><v>'.($value * 1).'</v></c>');
573
                } else {
574
                    $file->write(
575
                        '<c r="'.$cellName.'" s="'.$cellIndex.'" t="s"><v>'.self::xmlspecialchars(
576
                            $this->setSharedString($value)
577
                        ).'</v></c>'
578
                    );
579
                }
580
            }
581
        }
582
    }
583
584
    /**
585
     * @return string
586
     */
587
    protected function writeStylesXML()
588
    {
589
        $temporaryFilename = $this->tempFilename();
590
        $file = new Writer($temporaryFilename);
591
        $file->write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n");
592
        $file->write('<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">');
593
        $file->write('<numFmts count="'.count($this->cellFormats).'">');
594
        foreach ($this->cellFormats as $i => $v) {
595
            $file->write('<numFmt numFmtId="'.(164 + $i).'" formatCode="'.self::xmlspecialchars($v).'" />');
596
        }
597
        $file->write('</numFmts>');
598
        $file->write('<fonts count="4">');
599
        $file->write('<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>');
600
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
601
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
602
        $file->write('<font><name val="Arial"/><family val="0"/><sz val="10"/></font>');
603
        $file->write('</fonts>');
604
        $file->write('<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>');
605
        $file->write('<borders count="1"><border diagonalDown="false" diagonalUp="false"><left/><right/><top/><bottom/><diagonal/></border></borders>');
606
        $file->write('<cellStyleXfs count="20">');
607
        $file->write('<xf applyAlignment="true" applyBorder="true" applyFont="true" applyProtection="true" borderId="0" fillId="0" fontId="0" numFmtId="164">');
608
        $file->write('<alignment horizontal="general" indent="0" shrinkToFit="false" textRotation="0" vertical="bottom" wrapText="false"/>');
609
        $file->write('<protection hidden="false" locked="true"/>');
610
        $file->write('</xf>');
611
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
612
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>');
613
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
614
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>');
615
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
616
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
617
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
618
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
619
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
620
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
621
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
622
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
623
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
624
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>');
625
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="43"/>');
626
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="41"/>');
627
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="44"/>');
628
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="42"/>');
629
        $file->write('<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="9"/>');
630
        $file->write('</cellStyleXfs>');
631
        $file->write('<cellXfs count="'.count($this->cellFormats).'">');
632
        foreach ($this->cellFormats as $i => $v) {
633
            $file->write(
634
                '<xf applyAlignment="false" applyBorder="false" applyFont="false" 
635
            applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="'.(164 + $i).'" xfId="0"/>'
636
            );
637
        }
638
        $file->write('</cellXfs>');
639
        $file->write('<cellStyles count="6">');
640
        $file->write('<cellStyle builtinId="0" customBuiltin="false" name="Normal" xfId="0"/>');
641
        $file->write('<cellStyle builtinId="3" customBuiltin="false" name="Comma" xfId="15"/>');
642
        $file->write('<cellStyle builtinId="6" customBuiltin="false" name="Comma [0]" xfId="16"/>');
643
        $file->write('<cellStyle builtinId="4" customBuiltin="false" name="Currency" xfId="17"/>');
644
        $file->write('<cellStyle builtinId="7" customBuiltin="false" name="Currency [0]" xfId="18"/>');
645
        $file->write('<cellStyle builtinId="5" customBuiltin="false" name="Percent" xfId="19"/>');
646
        $file->write('</cellStyles>');
647
        $file->write('</styleSheet>');
648
        $file->close();
649
        return $temporaryFilename;
650
    }
651
652
    /**
653
     * @param $v
654
     *
655
     * @return int|mixed
656
     */
657
    protected function setSharedString($v)
658
    {
659
        if (isset($this->sharedStrings[$v])) {
660
            $stringValue = $this->sharedStrings[$v];
661
        } else {
662
            $stringValue = count($this->sharedStrings);
663
            $this->sharedStrings[$v] = $stringValue;
664
        }
665
        $this->sharedStringCount++;
666
667
        return $stringValue;
668
    }
669
670
    /**
671
     * @return string
672
     */
673
    protected function writeSharedStringsXML()
674
    {
675
        $tempFilename = $this->tempFilename();
676
        $file = new Writer($tempFilename);
677
        $sharedStrings = new SharedStrings($this->sharedStringCount, $this->sharedStrings);
678
        $file->write($sharedStrings->buildSharedStringsXML());
679
        $file->close();
680
681
        return $tempFilename;
682
    }
683
684
    /**
685
     * @param int $rowNumber
686
     * @param int $columnNumber
687
     *
688
     * @return string Cell label/coordinates (A1, C3, AA42)
689
     */
690
    public static function xlsCell($rowNumber, $columnNumber)
691
    {
692
        $n = $columnNumber;
693
        for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
694
            $r = chr($n % 26 + 0x41).$r;
695
        }
696
697
        return $r.($rowNumber + 1);
698
    }
699
700
    /**
701
     * @param $string
702
     */
703
    public static function log($string)
704
    {
705
        file_put_contents(
706
            "php://stderr",
707
            date("Y-m-d H:i:s:").rtrim(is_array($string) ? json_encode($string) : $string)."\n"
708
        );
709
    }
710
711
    /**
712
     * @link https://msdn.microsoft.com/ru-RU/library/aa365247%28VS.85%29.aspx
713
     *
714
     * @param string $filename
715
     *
716
     * @return mixed
717
     */
718
    public static function checkFilename($filename)
719
    {
720
        $invalidCharacter = array_merge(
721
            array_map('chr', range(0, 31)),
722
            ['<', '>', '?', '"', ':', '|', '\\', '/', '*', '&']
723
        );
724
725
        return str_replace($invalidCharacter, '', $filename);
726
    }
727
728
    /**
729
     * @param $val
730
     *
731
     * @return mixed
732
     */
733
    public static function xmlspecialchars($val)
734
    {
735
        return str_replace("'", "&#39;", htmlspecialchars($val));
736
    }
737
738
    /**
739
     * @param string $dateInput
740
     *
741
     * @return int
742
     */
743
    public static function convertDateTime($dateInput)
744
    {
745
        // Time expressed as fraction of 24h hours in seconds
746
        $seconds = 0;
747
        $year = $month = $day = 0;
748
        $dateTime = $dateInput;
749
        if (preg_match("/(\d{4})\-(\d{2})\-(\d{2})/", $dateTime, $matches)) {
750
            list($year, $month, $day) = $matches;
751
        }
752
        if (preg_match("/(\d{2}):(\d{2}):(\d{2})/", $dateTime, $matches)) {
753
            list($hour, $min, $sec) = $matches;
754
            $seconds = ($hour * 60 * 60 + $min * 60 + $sec) / (24 * 60 * 60);
755
        }
756
        //using 1900 as epoch, not 1904, ignoring 1904 special case
757
        // Special cases for Excel.
758
        if ("$year-$month-$day" == '1899-12-31') {
759
            return $seconds;
760
        }    // Excel 1900 epoch
761
        if ("$year-$month-$day" == '1900-01-00') {
762
            return $seconds;
763
        }    // Excel 1900 epoch
764
        if ("$year-$month-$day" == '1900-02-29') {
765
            return 60 + $seconds;
766
        }
767
        // Excel false leapday
768
        /*
769
         We calculate the date by calculating the number of days since the epoch
770
         and adjust for the number of leap days. We calculate the number of leap
771
         days by normalising the year in relation to the epoch. Thus the year 2000
772
         becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
773
        */
774
        $epoch  = 1900;
775
        $offset = 0;
776
        $norm   = 300;
777
        $range  = $year - $epoch;
778
        // Set month days and check for leap year.
779
        $leap = (($year % 400 == 0) || (($year % 4 == 0) && ($year % 100)) ) ? 1 : 0;
780
        $mdays = array( 31, ($leap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
781
        // Some boundary checks
782
        if ($year < $epoch || $year > 9999) {
783
            return 0;
784
        }
785
        if ($month < 1 || $month > 12) {
786
            return 0;
787
        }
788
        if ($day < 1 || $day > $mdays[$month - 1]) {
789
            return 0;
790
        }
791
        // Accumulate the number of days since the epoch.
792
        // Add days for current month
793
        $days = $day;
794
        // Add days for past months
795
        $days += array_sum(array_slice($mdays, 0, $month - 1));
796
        // Add days for past years
797
        $days += $range * 365;
798
        // Add leapdays
799
        $days += intval(($range) / 4);
800
        // Subtract 100 year leapdays
801
        $days -= intval(($range + $offset) / 100);
802
        // Add 400 year leapdays
803
        $days += intval(($range + $offset + $norm) / 400);
804
        // Already counted above
805
        $days -= $leap;
806
        // Adjust for Excel erroneously treating 1900 as a leap year.
807
        if ($days > 59) {
808
            $days++;
809
        }
810
811
        return $days + $seconds;
812
    }
813
}
814