Completed
Push — develop ( 5dd185...41a1aa )
by Adrien
21:47 queued 14:23
created

Workbook   F

Complexity

Total Complexity 103

Size/Duplication

Total Lines 1390
Duplicated Lines 6.69 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 58.35%

Importance

Changes 0
Metric Value
dl 93
loc 1390
rs 0.5217
c 0
b 0
f 0
ccs 332
cts 569
cp 0.5835
wmc 103
lcom 1
cbo 15

35 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 37 3
B addXfWriter() 0 44 3
A addFont() 0 18 3
B addColor() 0 25 3
A setPaletteXl97() 0 61 1
A writeWorkbook() 0 48 3
A calcSheetOffsets() 0 20 3
A writeAllFonts() 0 6 2
A writeAllNumberFormats() 0 6 2
A writeAllXfs() 0 6 2
A writeAllStyles() 0 4 1
A writeExternals() 0 11 2
C writeNames() 16 88 10
D writeAllDefinedNamesBiff8() 22 145 23
B writeDefinedNameBiff8() 0 25 2
B writeShortNameBiff8() 0 29 2
A writeCodepage() 0 11 1
B writeWindow1() 0 25 1
B writeBoundSheet() 0 33 4
A writeSupbookInternal() 9 10 1
A writeExternalsheetBiff8() 0 15 2
A writeStyle() 0 13 1
A writeNumberFormat() 11 11 1
A writeDateMode() 0 13 2
A writeExternalCount() 0 9 1
A writeExternalSheet() 12 12 1
A writeNameShort() 0 50 1
A writeNameLong() 0 69 1
A writeCountry() 11 11 1
A writeRecalcId() 12 12 1
A writePalette() 0 19 3
C writeSharedStringsTable() 0 110 12
A writeMsoDrawingGroup() 0 16 2
A getEscher() 0 4 1
A setEscher() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Workbook often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Workbook, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
4
5
use PhpOffice\PhpSpreadsheet\Cell;
6
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
7
use PhpOffice\PhpSpreadsheet\Shared\Date;
8
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
9
use PhpOffice\PhpSpreadsheet\Spreadsheet;
10
11
/**
12
 * Copyright (c) 2006 - 2015 PhpSpreadsheet.
13
 *
14
 * This library is free software; you can redistribute it and/or
15
 * modify it under the terms of the GNU Lesser General Public
16
 * License as published by the Free Software Foundation; either
17
 * version 2.1 of the License, or (at your option) any later version.
18
 *
19
 * This library is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22
 * Lesser General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Lesser General Public
25
 * License along with this library; if not, write to the Free Software
26
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27
 *
28
 * @category   PhpSpreadsheet
29
 *
30
 * @copyright  Copyright (c) 2006 - 2015 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
31
 * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
32
 */
33
34
// Original file header of PEAR::Spreadsheet_Excel_Writer_Workbook (used as the base for this class):
35
// -----------------------------------------------------------------------------------------
36
// /*
37
// *  Module written/ported by Xavier Noguer <[email protected]>
38
// *
39
// *  The majority of this is _NOT_ my code.  I simply ported it from the
40
// *  PERL Spreadsheet::WriteExcel module.
41
// *
42
// *  The author of the Spreadsheet::WriteExcel module is John McNamara
43
// *  <[email protected]>
44
// *
45
// *  I _DO_ maintain this code, and John McNamara has nothing to do with the
46
// *  porting of this code to PHP.  Any questions directly related to this
47
// *  class library should be directed to me.
48
// *
49
// *  License Information:
50
// *
51
// *    Spreadsheet_Excel_Writer:  A library for generating Excel Spreadsheets
52
// *    Copyright (c) 2002-2003 Xavier Noguer [email protected]
53
// *
54
// *    This library is free software; you can redistribute it and/or
55
// *    modify it under the terms of the GNU Lesser General Public
56
// *    License as published by the Free Software Foundation; either
57
// *    version 2.1 of the License, or (at your option) any later version.
58
// *
59
// *    This library is distributed in the hope that it will be useful,
60
// *    but WITHOUT ANY WARRANTY; without even the implied warranty of
61
// *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
62
// *    Lesser General Public License for more details.
63
// *
64
// *    You should have received a copy of the GNU Lesser General Public
65
// *    License along with this library; if not, write to the Free Software
66
// *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
67
// */
68
class Workbook extends BIFFwriter
69
{
70
    /**
71
     * Formula parser.
72
     *
73
     * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
74
     */
75
    private $parser;
76
77
    /**
78
     * The BIFF file size for the workbook.
79
     *
80
     * @var int
81
     *
82
     * @see calcSheetOffsets()
83
     */
84
    private $biffSize;
85
86
    /**
87
     * XF Writers.
88
     *
89
     * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Xf[]
90
     */
91
    private $xfWriters = [];
92
93
    /**
94
     * Array containing the colour palette.
95
     *
96
     * @var array
97
     */
98
    private $palette;
99
100
    /**
101
     * The codepage indicates the text encoding used for strings.
102
     *
103
     * @var int
104
     */
105
    private $codepage;
106
107
    /**
108
     * The country code used for localization.
109
     *
110
     * @var int
111
     */
112
    private $countryCode;
113
114
    /**
115
     * Workbook.
116
     *
117
     * @var Spreadsheet
118
     */
119
    private $spreadsheet;
120
121
    /**
122
     * Fonts writers.
123
     *
124
     * @var Font[]
125
     */
126
    private $fontWriters = [];
127
128
    /**
129
     * Added fonts. Maps from font's hash => index in workbook.
130
     *
131
     * @var array
132
     */
133
    private $addedFonts = [];
134
135
    /**
136
     * Shared number formats.
137
     *
138
     * @var array
139
     */
140
    private $numberFormats = [];
141
142
    /**
143
     * Added number formats. Maps from numberFormat's hash => index in workbook.
144
     *
145
     * @var array
146
     */
147
    private $addedNumberFormats = [];
148
149
    /**
150
     * Sizes of the binary worksheet streams.
151
     *
152
     * @var array
153
     */
154
    private $worksheetSizes = [];
155
156
    /**
157
     * Offsets of the binary worksheet streams relative to the start of the global workbook stream.
158
     *
159
     * @var array
160
     */
161
    private $worksheetOffsets = [];
162
163
    /**
164
     * Total number of shared strings in workbook.
165
     *
166
     * @var int
167
     */
168
    private $stringTotal;
169
170
    /**
171
     * Number of unique shared strings in workbook.
172
     *
173
     * @var int
174
     */
175
    private $stringUnique;
176
177
    /**
178
     * Array of unique shared strings in workbook.
179
     *
180
     * @var array
181
     */
182
    private $stringTable;
183
184
    /**
185
     * Color cache.
186
     */
187
    private $colors;
188
189
    /**
190
     * Escher object corresponding to MSODRAWINGGROUP.
191
     *
192
     * @var \PhpOffice\PhpSpreadsheet\Shared\Escher
193
     */
194
    private $escher;
195
196
    /**
197
     * Class constructor.
198
     *
199
     * @param Spreadsheet $spreadsheet The Workbook
200
     * @param int $str_total Total number of strings
201
     * @param int $str_unique Total number of unique strings
202
     * @param array $str_table String Table
203
     * @param array $colors Colour Table
204
     * @param Parser $parser The formula parser created for the Workbook
205
     */
206 39
    public function __construct(Spreadsheet $spreadsheet, &$str_total, &$str_unique, &$str_table, &$colors, Parser $parser)
207
    {
208
        // It needs to call its parent's constructor explicitly
209 39
        parent::__construct();
210
211 39
        $this->parser = $parser;
212 39
        $this->biffSize = 0;
213 39
        $this->palette = [];
214 39
        $this->countryCode = -1;
215
216 39
        $this->stringTotal = &$str_total;
217 39
        $this->stringUnique = &$str_unique;
218 39
        $this->stringTable = &$str_table;
219 39
        $this->colors = &$colors;
220 39
        $this->setPaletteXl97();
221
222 39
        $this->spreadsheet = $spreadsheet;
223
224 39
        $this->codepage = 0x04B0;
225
226
        // Add empty sheets and Build color cache
227 39
        $countSheets = $spreadsheet->getSheetCount();
228 39
        for ($i = 0; $i < $countSheets; ++$i) {
229 39
            $phpSheet = $spreadsheet->getSheet($i);
230
231 39
            $this->parser->setExtSheet($phpSheet->getTitle(), $i); // Register worksheet name with parser
232
233 39
            $supbook_index = 0x00;
234 39
            $ref = pack('vvv', $supbook_index, $i, $i);
235 39
            $this->parser->references[] = $ref; // Register reference with parser
236
237
            // Sheet tab colors?
238 39
            if ($phpSheet->isTabColorSet()) {
239 5
                $this->addColor($phpSheet->getTabColor()->getRGB());
240
            }
241
        }
242 39
    }
243
244
    /**
245
     * Add a new XF writer.
246
     *
247
     * @param \PhpOffice\PhpSpreadsheet\Style
248
     * @param bool Is it a style XF?
249
     * @param mixed $style
250
     * @param mixed $isStyleXf
251
     *
252
     * @return int Index to XF record
253
     */
254 39
    public function addXfWriter($style, $isStyleXf = false)
255
    {
256 39
        $xfWriter = new Xf($style);
257 39
        $xfWriter->setIsStyleXf($isStyleXf);
258
259
        // Add the font if not already added
260 39
        $fontIndex = $this->addFont($style->getFont());
261
262
        // Assign the font index to the xf record
263 39
        $xfWriter->setFontIndex($fontIndex);
264
265
        // Background colors, best to treat these after the font so black will come after white in custom palette
266 39
        $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB()));
267 39
        $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB()));
268 39
        $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB()));
269 39
        $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB()));
270 39
        $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB()));
271 39
        $xfWriter->setLeftColor($this->addColor($style->getBorders()->getLeft()->getColor()->getRGB()));
272 39
        $xfWriter->setDiagColor($this->addColor($style->getBorders()->getDiagonal()->getColor()->getRGB()));
273
274
        // Add the number format if it is not a built-in one and not already added
275 39
        if ($style->getNumberFormat()->getBuiltInFormatCode() === false) {
276 14
            $numberFormatHashCode = $style->getNumberFormat()->getHashCode();
277
278 14
            if (isset($this->addedNumberFormats[$numberFormatHashCode])) {
279 6
                $numberFormatIndex = $this->addedNumberFormats[$numberFormatHashCode];
280
            } else {
281 14
                $numberFormatIndex = 164 + count($this->numberFormats);
282 14
                $this->numberFormats[$numberFormatIndex] = $style->getNumberFormat();
283 14
                $this->addedNumberFormats[$numberFormatHashCode] = $numberFormatIndex;
284
            }
285
        } else {
286 39
            $numberFormatIndex = (int) $style->getNumberFormat()->getBuiltInFormatCode();
287
        }
288
289
        // Assign the number format index to xf record
290 39
        $xfWriter->setNumberFormatIndex($numberFormatIndex);
291
292 39
        $this->xfWriters[] = $xfWriter;
293
294 39
        $xfIndex = count($this->xfWriters) - 1;
295
296 39
        return $xfIndex;
297
    }
298
299
    /**
300
     * Add a font to added fonts.
301
     *
302
     * @param \PhpOffice\PhpSpreadsheet\Style\Font $font
303
     *
304
     * @return int Index to FONT record
305
     */
306 39
    public function addFont(\PhpOffice\PhpSpreadsheet\Style\Font $font)
307
    {
308 39
        $fontHashCode = $font->getHashCode();
309 39
        if (isset($this->addedFonts[$fontHashCode])) {
310 39
            $fontIndex = $this->addedFonts[$fontHashCode];
311
        } else {
312 39
            $countFonts = count($this->fontWriters);
313 39
            $fontIndex = ($countFonts < 4) ? $countFonts : $countFonts + 1;
314
315 39
            $fontWriter = new Font($font);
316 39
            $fontWriter->setColorIndex($this->addColor($font->getColor()->getRGB()));
317 39
            $this->fontWriters[] = $fontWriter;
318
319 39
            $this->addedFonts[$fontHashCode] = $fontIndex;
320
        }
321
322 39
        return $fontIndex;
323
    }
324
325
    /**
326
     * Alter color palette adding a custom color.
327
     *
328
     * @param string $rgb E.g. 'FF00AA'
329
     *
330
     * @return int Color index
331
     */
332 39
    private function addColor($rgb)
333
    {
334 39
        if (!isset($this->colors[$rgb])) {
335 39
            if (count($this->colors) < 57) {
336
                // then we add a custom color altering the palette
337 39
                $colorIndex = 8 + count($this->colors);
338 39
                $this->palette[$colorIndex] =
339
                    [
340 39
                        hexdec(substr($rgb, 0, 2)),
341 39
                        hexdec(substr($rgb, 2, 2)),
342 39
                        hexdec(substr($rgb, 4)),
343 39
                        0,
344
                    ];
345 39
                $this->colors[$rgb] = $colorIndex;
346
            } else {
347
                // no room for more custom colors, just map to black
348 39
                $colorIndex = 0;
349
            }
350
        } else {
351
            // fetch already added custom color
352 39
            $colorIndex = $this->colors[$rgb];
353
        }
354
355 39
        return $colorIndex;
356
    }
357
358
    /**
359
     * Sets the colour palette to the Excel 97+ default.
360
     */
361 39
    private function setPaletteXl97()
362
    {
363 39
        $this->palette = [
364
            0x08 => [0x00, 0x00, 0x00, 0x00],
365
            0x09 => [0xff, 0xff, 0xff, 0x00],
366
            0x0A => [0xff, 0x00, 0x00, 0x00],
367
            0x0B => [0x00, 0xff, 0x00, 0x00],
368
            0x0C => [0x00, 0x00, 0xff, 0x00],
369
            0x0D => [0xff, 0xff, 0x00, 0x00],
370
            0x0E => [0xff, 0x00, 0xff, 0x00],
371
            0x0F => [0x00, 0xff, 0xff, 0x00],
372
            0x10 => [0x80, 0x00, 0x00, 0x00],
373
            0x11 => [0x00, 0x80, 0x00, 0x00],
374
            0x12 => [0x00, 0x00, 0x80, 0x00],
375
            0x13 => [0x80, 0x80, 0x00, 0x00],
376
            0x14 => [0x80, 0x00, 0x80, 0x00],
377
            0x15 => [0x00, 0x80, 0x80, 0x00],
378
            0x16 => [0xc0, 0xc0, 0xc0, 0x00],
379
            0x17 => [0x80, 0x80, 0x80, 0x00],
380
            0x18 => [0x99, 0x99, 0xff, 0x00],
381
            0x19 => [0x99, 0x33, 0x66, 0x00],
382
            0x1A => [0xff, 0xff, 0xcc, 0x00],
383
            0x1B => [0xcc, 0xff, 0xff, 0x00],
384
            0x1C => [0x66, 0x00, 0x66, 0x00],
385
            0x1D => [0xff, 0x80, 0x80, 0x00],
386
            0x1E => [0x00, 0x66, 0xcc, 0x00],
387
            0x1F => [0xcc, 0xcc, 0xff, 0x00],
388
            0x20 => [0x00, 0x00, 0x80, 0x00],
389
            0x21 => [0xff, 0x00, 0xff, 0x00],
390
            0x22 => [0xff, 0xff, 0x00, 0x00],
391
            0x23 => [0x00, 0xff, 0xff, 0x00],
392
            0x24 => [0x80, 0x00, 0x80, 0x00],
393
            0x25 => [0x80, 0x00, 0x00, 0x00],
394
            0x26 => [0x00, 0x80, 0x80, 0x00],
395
            0x27 => [0x00, 0x00, 0xff, 0x00],
396
            0x28 => [0x00, 0xcc, 0xff, 0x00],
397
            0x29 => [0xcc, 0xff, 0xff, 0x00],
398
            0x2A => [0xcc, 0xff, 0xcc, 0x00],
399
            0x2B => [0xff, 0xff, 0x99, 0x00],
400
            0x2C => [0x99, 0xcc, 0xff, 0x00],
401
            0x2D => [0xff, 0x99, 0xcc, 0x00],
402
            0x2E => [0xcc, 0x99, 0xff, 0x00],
403
            0x2F => [0xff, 0xcc, 0x99, 0x00],
404
            0x30 => [0x33, 0x66, 0xff, 0x00],
405
            0x31 => [0x33, 0xcc, 0xcc, 0x00],
406
            0x32 => [0x99, 0xcc, 0x00, 0x00],
407
            0x33 => [0xff, 0xcc, 0x00, 0x00],
408
            0x34 => [0xff, 0x99, 0x00, 0x00],
409
            0x35 => [0xff, 0x66, 0x00, 0x00],
410
            0x36 => [0x66, 0x66, 0x99, 0x00],
411
            0x37 => [0x96, 0x96, 0x96, 0x00],
412
            0x38 => [0x00, 0x33, 0x66, 0x00],
413
            0x39 => [0x33, 0x99, 0x66, 0x00],
414
            0x3A => [0x00, 0x33, 0x00, 0x00],
415
            0x3B => [0x33, 0x33, 0x00, 0x00],
416
            0x3C => [0x99, 0x33, 0x00, 0x00],
417
            0x3D => [0x99, 0x33, 0x66, 0x00],
418
            0x3E => [0x33, 0x33, 0x99, 0x00],
419
            0x3F => [0x33, 0x33, 0x33, 0x00],
420
        ];
421 39
    }
422
423
    /**
424
     * Assemble worksheets into a workbook and send the BIFF data to an OLE
425
     * storage.
426
     *
427
     * @param array $pWorksheetSizes The sizes in bytes of the binary worksheet streams
428
     *
429
     * @return string Binary data for workbook stream
430
     */
431 39
    public function writeWorkbook(array $pWorksheetSizes)
432
    {
433 39
        $this->worksheetSizes = $pWorksheetSizes;
434
435
        // Calculate the number of selected worksheet tabs and call the finalization
436
        // methods for each worksheet
437 39
        $total_worksheets = $this->spreadsheet->getSheetCount();
438
439
        // Add part 1 of the Workbook globals, what goes before the SHEET records
440 39
        $this->storeBof(0x0005);
441 39
        $this->writeCodepage();
442 39
        $this->writeWindow1();
443
444 39
        $this->writeDateMode();
445 39
        $this->writeAllFonts();
446 39
        $this->writeAllNumberFormats();
447 39
        $this->writeAllXfs();
448 39
        $this->writeAllStyles();
449 39
        $this->writePalette();
450
451
        // Prepare part 3 of the workbook global stream, what goes after the SHEET records
452 39
        $part3 = '';
453 39
        if ($this->countryCode != -1) {
454
            $part3 .= $this->writeCountry();
455
        }
456 39
        $part3 .= $this->writeRecalcId();
457
458 39
        $part3 .= $this->writeSupbookInternal();
459
        /* TODO: store external SUPBOOK records and XCT and CRN records
460
        in case of external references for BIFF8 */
461 39
        $part3 .= $this->writeExternalsheetBiff8();
462 39
        $part3 .= $this->writeAllDefinedNamesBiff8();
463 39
        $part3 .= $this->writeMsoDrawingGroup();
464 39
        $part3 .= $this->writeSharedStringsTable();
465
466 39
        $part3 .= $this->writeEof();
467
468
        // Add part 2 of the Workbook globals, the SHEET records
469 39
        $this->calcSheetOffsets();
470 39
        for ($i = 0; $i < $total_worksheets; ++$i) {
471 39
            $this->writeBoundSheet($this->spreadsheet->getSheet($i), $this->worksheetOffsets[$i]);
472
        }
473
474
        // Add part 3 of the Workbook globals
475 39
        $this->_data .= $part3;
476
477 39
        return $this->_data;
478
    }
479
480
    /**
481
     * Calculate offsets for Worksheet BOF records.
482
     */
483 39
    private function calcSheetOffsets()
484
    {
485 39
        $boundsheet_length = 10; // fixed length for a BOUNDSHEET record
486
487
        // size of Workbook globals part 1 + 3
488 39
        $offset = $this->_datasize;
489
490
        // add size of Workbook globals part 2, the length of the SHEET records
491 39
        $total_worksheets = count($this->spreadsheet->getAllSheets());
492 39
        foreach ($this->spreadsheet->getWorksheetIterator() as $sheet) {
493 39
            $offset += $boundsheet_length + strlen(StringHelper::UTF8toBIFF8UnicodeShort($sheet->getTitle()));
494
        }
495
496
        // add the sizes of each of the Sheet substreams, respectively
497 39
        for ($i = 0; $i < $total_worksheets; ++$i) {
498 39
            $this->worksheetOffsets[$i] = $offset;
499 39
            $offset += $this->worksheetSizes[$i];
500
        }
501 39
        $this->biffSize = $offset;
502 39
    }
503
504
    /**
505
     * Store the Excel FONT records.
506
     */
507 39
    private function writeAllFonts()
508
    {
509 39
        foreach ($this->fontWriters as $fontWriter) {
510 39
            $this->append($fontWriter->writeFont());
511
        }
512 39
    }
513
514
    /**
515
     * Store user defined numerical formats i.e. FORMAT records.
516
     */
517 39
    private function writeAllNumberFormats()
518
    {
519 39
        foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) {
520 14
            $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex);
521
        }
522 39
    }
523
524
    /**
525
     * Write all XF records.
526
     */
527 39
    private function writeAllXfs()
528
    {
529 39
        foreach ($this->xfWriters as $xfWriter) {
530 39
            $this->append($xfWriter->writeXf());
531
        }
532 39
    }
533
534
    /**
535
     * Write all STYLE records.
536
     */
537 39
    private function writeAllStyles()
538
    {
539 39
        $this->writeStyle();
540 39
    }
541
542
    /**
543
     * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
544
     * the NAME records.
545
     */
546
    private function writeExternals()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
547
    {
548
        $countSheets = $this->spreadsheet->getSheetCount();
549
        // Create EXTERNCOUNT with number of worksheets
550
        $this->writeExternalCount($countSheets);
551
552
        // Create EXTERNSHEET for each worksheet
553
        for ($i = 0; $i < $countSheets; ++$i) {
554
            $this->writeExternalSheet($this->spreadsheet->getSheet($i)->getTitle());
555
        }
556
    }
557
558
    /**
559
     * Write the NAME record to define the print area and the repeat rows and cols.
560
     */
561
    private function writeNames()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
562
    {
563
        // total number of sheets
564
        $total_worksheets = $this->spreadsheet->getSheetCount();
565
566
        // Create the print area NAME records
567
        for ($i = 0; $i < $total_worksheets; ++$i) {
568
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
569
            // Write a Name record if the print area has been defined
570
            if ($sheetSetup->isPrintAreaSet()) {
571
                // Print area
572
                $printArea = Cell::splitRange($sheetSetup->getPrintArea());
573
                $printArea = $printArea[0];
574
                $printArea[0] = Cell::coordinateFromString($printArea[0]);
575
                $printArea[1] = Cell::coordinateFromString($printArea[1]);
576
577
                $print_rowmin = $printArea[0][1] - 1;
578
                $print_rowmax = $printArea[1][1] - 1;
579
                $print_colmin = Cell::columnIndexFromString($printArea[0][0]) - 1;
580
                $print_colmax = Cell::columnIndexFromString($printArea[1][0]) - 1;
581
582
                $this->writeNameShort(
583
                    $i, // sheet index
584
                    0x06, // NAME type
585
                    $print_rowmin,
586
                    $print_rowmax,
587
                    $print_colmin,
588
                    $print_colmax
589
                );
590
            }
591
        }
592
593
        // Create the print title NAME records
594
        for ($i = 0; $i < $total_worksheets; ++$i) {
595
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
596
597
            // simultaneous repeatColumns repeatRows
598
            if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
599
                $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
600
                $colmin = Cell::columnIndexFromString($repeat[0]) - 1;
601
                $colmax = Cell::columnIndexFromString($repeat[1]) - 1;
602
603
                $repeat = $sheetSetup->getRowsToRepeatAtTop();
604
                $rowmin = $repeat[0] - 1;
605
                $rowmax = $repeat[1] - 1;
606
607
                $this->writeNameLong(
608
                    $i, // sheet index
609
                    0x07, // NAME type
610
                    $rowmin,
611
                    $rowmax,
612
                    $colmin,
613
                    $colmax
614
                );
615
616
            // (exclusive) either repeatColumns or repeatRows
617
            } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
618
                // Columns to repeat
619 View Code Duplication
                if ($sheetSetup->isColumnsToRepeatAtLeftSet()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
620
                    $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
621
                    $colmin = Cell::columnIndexFromString($repeat[0]) - 1;
622
                    $colmax = Cell::columnIndexFromString($repeat[1]) - 1;
623
                } else {
624
                    $colmin = 0;
625
                    $colmax = 255;
626
                }
627
628
                // Rows to repeat
629 View Code Duplication
                if ($sheetSetup->isRowsToRepeatAtTopSet()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
630
                    $repeat = $sheetSetup->getRowsToRepeatAtTop();
631
                    $rowmin = $repeat[0] - 1;
632
                    $rowmax = $repeat[1] - 1;
633
                } else {
634
                    $rowmin = 0;
635
                    $rowmax = 65535;
636
                }
637
638
                $this->writeNameShort(
639
                    $i, // sheet index
640
                    0x07, // NAME type
641
                    $rowmin,
642
                    $rowmax,
643
                    $colmin,
644
                    $colmax
645
                );
646
            }
647
        }
648
    }
649
650
    /**
651
     * Writes all the DEFINEDNAME records (BIFF8).
652
     * So far this is only used for repeating rows/columns (print titles) and print areas.
653
     */
654 39
    private function writeAllDefinedNamesBiff8()
655
    {
656 39
        $chunk = '';
657
658
        // Named ranges
659 39
        if (count($this->spreadsheet->getNamedRanges()) > 0) {
660
            // Loop named ranges
661 4
            $namedRanges = $this->spreadsheet->getNamedRanges();
662 4
            foreach ($namedRanges as $namedRange) {
663
                // Create absolute coordinate
664 4
                $range = Cell::splitRange($namedRange->getRange());
665 4 View Code Duplication
                for ($i = 0; $i < count($range); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
666 4
                    $range[$i][0] = '\'' . str_replace("'", "''", $namedRange->getWorksheet()->getTitle()) . '\'!' . Cell::absoluteCoordinate($range[$i][0]);
667 4
                    if (isset($range[$i][1])) {
668 3
                        $range[$i][1] = Cell::absoluteCoordinate($range[$i][1]);
669
                    }
670
                }
671 4
                $range = Cell::buildRange($range); // e.g. Sheet1!$A$1:$B$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
672
673
                // parse formula
674
                try {
675 4
                    $error = $this->parser->parse($range);
0 ignored issues
show
Unused Code introduced by
$error is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
676 4
                    $formulaData = $this->parser->toReversePolish();
677
678
                    // make sure tRef3d is of type tRef3dR (0x3A)
679 4
                    if (isset($formulaData[0]) and ($formulaData[0] == "\x7A" or $formulaData[0] == "\x5A")) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
680 1
                        $formulaData = "\x3A" . substr($formulaData, 1);
681
                    }
682
683 4
                    if ($namedRange->getLocalOnly()) {
684
                        // local scope
685
                        $scope = $this->spreadsheet->getIndex($namedRange->getScope()) + 1;
0 ignored issues
show
Bug introduced by
It seems like $namedRange->getScope() can be null; however, getIndex() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
686
                    } else {
687
                        // global scope
688 4
                        $scope = 0;
689
                    }
690 4
                    $chunk .= $this->writeData($this->writeDefinedNameBiff8($namedRange->getName(), $formulaData, $scope, false));
691 4
                } catch (PhpSpreadsheetException $e) {
692
                    // do nothing
693
                }
694
            }
695
        }
696
697
        // total number of sheets
698 39
        $total_worksheets = $this->spreadsheet->getSheetCount();
699
700
        // write the print titles (repeating rows, columns), if any
701 39
        for ($i = 0; $i < $total_worksheets; ++$i) {
702 39
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
703
            // simultaneous repeatColumns repeatRows
704 39
            if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
705
                $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
706
                $colmin = Cell::columnIndexFromString($repeat[0]) - 1;
707
                $colmax = Cell::columnIndexFromString($repeat[1]) - 1;
708
709
                $repeat = $sheetSetup->getRowsToRepeatAtTop();
710
                $rowmin = $repeat[0] - 1;
711
                $rowmax = $repeat[1] - 1;
712
713
                // construct formula data manually
714
                $formulaData = pack('Cv', 0x29, 0x17); // tMemFunc
715
                $formulaData .= pack('Cvvvvv', 0x3B, $i, 0, 65535, $colmin, $colmax); // tArea3d
716
                $formulaData .= pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, 0, 255); // tArea3d
717
                $formulaData .= pack('C', 0x10); // tList
718
719
                // store the DEFINEDNAME record
720
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
721
722
            // (exclusive) either repeatColumns or repeatRows
723 39
            } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
724
                // Columns to repeat
725 1 View Code Duplication
                if ($sheetSetup->isColumnsToRepeatAtLeftSet()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
726
                    $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
727
                    $colmin = Cell::columnIndexFromString($repeat[0]) - 1;
728
                    $colmax = Cell::columnIndexFromString($repeat[1]) - 1;
729
                } else {
730 1
                    $colmin = 0;
731 1
                    $colmax = 255;
732
                }
733
                // Rows to repeat
734 1 View Code Duplication
                if ($sheetSetup->isRowsToRepeatAtTopSet()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
735 1
                    $repeat = $sheetSetup->getRowsToRepeatAtTop();
736 1
                    $rowmin = $repeat[0] - 1;
737 1
                    $rowmax = $repeat[1] - 1;
738
                } else {
739
                    $rowmin = 0;
740
                    $rowmax = 65535;
741
                }
742
743
                // construct formula data manually because parser does not recognize absolute 3d cell references
744 1
                $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax);
745
746
                // store the DEFINEDNAME record
747 1
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
748
            }
749
        }
750
751
        // write the print areas, if any
752 39
        for ($i = 0; $i < $total_worksheets; ++$i) {
753 39
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
754 39
            if ($sheetSetup->isPrintAreaSet()) {
755
                // Print area, e.g. A3:J6,H1:X20
756
                $printArea = Cell::splitRange($sheetSetup->getPrintArea());
757
                $countPrintArea = count($printArea);
758
759
                $formulaData = '';
760
                for ($j = 0; $j < $countPrintArea; ++$j) {
761
                    $printAreaRect = $printArea[$j]; // e.g. A3:J6
762
                    $printAreaRect[0] = Cell::coordinateFromString($printAreaRect[0]);
763
                    $printAreaRect[1] = Cell::coordinateFromString($printAreaRect[1]);
764
765
                    $print_rowmin = $printAreaRect[0][1] - 1;
766
                    $print_rowmax = $printAreaRect[1][1] - 1;
767
                    $print_colmin = Cell::columnIndexFromString($printAreaRect[0][0]) - 1;
768
                    $print_colmax = Cell::columnIndexFromString($printAreaRect[1][0]) - 1;
769
770
                    // construct formula data manually because parser does not recognize absolute 3d cell references
771
                    $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax);
772
773
                    if ($j > 0) {
774
                        $formulaData .= pack('C', 0x10); // list operator token ','
775
                    }
776
                }
777
778
                // store the DEFINEDNAME record
779
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x06), $formulaData, $i + 1, true));
780
            }
781
        }
782
783
        // write autofilters, if any
784 39
        for ($i = 0; $i < $total_worksheets; ++$i) {
785 39
            $sheetAutoFilter = $this->spreadsheet->getSheet($i)->getAutoFilter();
786 39
            $autoFilterRange = $sheetAutoFilter->getRange();
787 39
            if (!empty($autoFilterRange)) {
788 3
                $rangeBounds = Cell::rangeBoundaries($autoFilterRange);
789
790
                //Autofilter built in name
791 3
                $name = pack('C', 0x0D);
792
793 3
                $chunk .= $this->writeData($this->writeShortNameBiff8($name, $i + 1, $rangeBounds, true));
794
            }
795
        }
796
797 39
        return $chunk;
798
    }
799
800
    /**
801
     * Write a DEFINEDNAME record for BIFF8 using explicit binary formula data.
802
     *
803
     * @param string $name The name in UTF-8
804
     * @param string $formulaData The binary formula data
805
     * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global
806
     * @param bool $isBuiltIn Built-in name?
807
     *
808
     * @return string Complete binary record data
809
     */
810 5
    private function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false)
811
    {
812 5
        $record = 0x0018;
813
814
        // option flags
815 5
        $options = $isBuiltIn ? 0x20 : 0x00;
816
817
        // length of the name, character count
818 5
        $nlen = StringHelper::countCharacters($name);
819
820
        // name with stripped length field
821 5
        $name = substr(StringHelper::UTF8toBIFF8UnicodeLong($name), 2);
822
823
        // size of the formula (in bytes)
824 5
        $sz = strlen($formulaData);
825
826
        // combine the parts
827 5
        $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0)
828 5
            . $name . $formulaData;
829 5
        $length = strlen($data);
830
831 5
        $header = pack('vv', $record, $length);
832
833 5
        return $header . $data;
834
    }
835
836
    /**
837
     * Write a short NAME record.
838
     *
839
     * @param string $name
840
     * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global
841
     * @param integer[][] $rangeBounds range boundaries
842
     * @param bool $isHidden
843
     *
844
     * @return string Complete binary record data
845
     * */
846 3
    private function writeShortNameBiff8($name, $sheetIndex, $rangeBounds, $isHidden = false)
847
    {
848 3
        $record = 0x0018;
849
850
        // option flags
851 3
        $options = ($isHidden ? 0x21 : 0x00);
852
853 3
        $extra = pack(
854 3
            'Cvvvvv',
855 3
            0x3B,
856 3
            $sheetIndex - 1,
857 3
            $rangeBounds[0][1] - 1,
858 3
            $rangeBounds[1][1] - 1,
859 3
            $rangeBounds[0][0] - 1,
860 3
            $rangeBounds[1][0] - 1
861
        );
862
863
        // size of the formula (in bytes)
864 3
        $sz = strlen($extra);
865
866
        // combine the parts
867 3
        $data = pack('vCCvvvCCCCC', $options, 0, 1, $sz, 0, $sheetIndex, 0, 0, 0, 0, 0)
868 3
            . $name . $extra;
869 3
        $length = strlen($data);
870
871 3
        $header = pack('vv', $record, $length);
872
873 3
        return $header . $data;
874
    }
875
876
    /**
877
     * Stores the CODEPAGE biff record.
878
     */
879 39
    private function writeCodepage()
880
    {
881 39
        $record = 0x0042; // Record identifier
882 39
        $length = 0x0002; // Number of bytes to follow
883 39
        $cv = $this->codepage; // The code page
884
885 39
        $header = pack('vv', $record, $length);
886 39
        $data = pack('v', $cv);
887
888 39
        $this->append($header . $data);
889 39
    }
890
891
    /**
892
     * Write Excel BIFF WINDOW1 record.
893
     */
894 39
    private function writeWindow1()
895
    {
896 39
        $record = 0x003D; // Record identifier
897 39
        $length = 0x0012; // Number of bytes to follow
898
899 39
        $xWn = 0x0000; // Horizontal position of window
900 39
        $yWn = 0x0000; // Vertical position of window
901 39
        $dxWn = 0x25BC; // Width of window
902 39
        $dyWn = 0x1572; // Height of window
903
904 39
        $grbit = 0x0038; // Option flags
905
906
        // not supported by PhpSpreadsheet, so there is only one selected sheet, the active
907 39
        $ctabsel = 1; // Number of workbook tabs selected
908
909 39
        $wTabRatio = 0x0258; // Tab to scrollbar ratio
910
911
        // not supported by PhpSpreadsheet, set to 0
912 39
        $itabFirst = 0; // 1st displayed worksheet
913 39
        $itabCur = $this->spreadsheet->getActiveSheetIndex(); // Active worksheet
914
915 39
        $header = pack('vv', $record, $length);
916 39
        $data = pack('vvvvvvvvv', $xWn, $yWn, $dxWn, $dyWn, $grbit, $itabCur, $itabFirst, $ctabsel, $wTabRatio);
917 39
        $this->append($header . $data);
918 39
    }
919
920
    /**
921
     * Writes Excel BIFF BOUNDSHEET record.
922
     *
923
     * @param \PhpOffice\PhpSpreadsheet\Worksheet $sheet Worksheet name
924
     * @param int $offset Location of worksheet BOF
925
     */
926 39
    private function writeBoundSheet($sheet, $offset)
927
    {
928 39
        $sheetname = $sheet->getTitle();
929 39
        $record = 0x0085; // Record identifier
930
931
        // sheet state
932 39
        switch ($sheet->getSheetState()) {
933 39
            case \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_VISIBLE:
934 39
                $ss = 0x00;
935 39
                break;
936
            case \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_HIDDEN:
937
                $ss = 0x01;
938
                break;
939
            case \PhpOffice\PhpSpreadsheet\Worksheet::SHEETSTATE_VERYHIDDEN:
940
                $ss = 0x02;
941
                break;
942
            default:
943
                $ss = 0x00;
944
                break;
945
        }
946
947
        // sheet type
948 39
        $st = 0x00;
949
950 39
        $grbit = 0x0000; // Visibility and sheet type
0 ignored issues
show
Unused Code introduced by
$grbit is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
951
952 39
        $data = pack('VCC', $offset, $ss, $st);
953 39
        $data .= StringHelper::UTF8toBIFF8UnicodeShort($sheetname);
954
955 39
        $length = strlen($data);
956 39
        $header = pack('vv', $record, $length);
957 39
        $this->append($header . $data);
958 39
    }
959
960
    /**
961
     * Write Internal SUPBOOK record.
962
     */
963 39 View Code Duplication
    private function writeSupbookInternal()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
964
    {
965 39
        $record = 0x01AE; // Record identifier
966 39
        $length = 0x0004; // Bytes to follow
967
968 39
        $header = pack('vv', $record, $length);
969 39
        $data = pack('vv', $this->spreadsheet->getSheetCount(), 0x0401);
970
971 39
        return $this->writeData($header . $data);
972
    }
973
974
    /**
975
     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
976
     * formulas.
977
     */
978 39
    private function writeExternalsheetBiff8()
979
    {
980 39
        $totalReferences = count($this->parser->references);
981 39
        $record = 0x0017; // Record identifier
982 39
        $length = 2 + 6 * $totalReferences; // Number of bytes to follow
983
984 39
        $supbook_index = 0; // FIXME: only using internal SUPBOOK record
0 ignored issues
show
Unused Code introduced by
$supbook_index is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
985 39
        $header = pack('vv', $record, $length);
986 39
        $data = pack('v', $totalReferences);
987 39
        for ($i = 0; $i < $totalReferences; ++$i) {
988 39
            $data .= $this->parser->references[$i];
989
        }
990
991 39
        return $this->writeData($header . $data);
992
    }
993
994
    /**
995
     * Write Excel BIFF STYLE records.
996
     */
997 39
    private function writeStyle()
998
    {
999 39
        $record = 0x0293; // Record identifier
1000 39
        $length = 0x0004; // Bytes to follow
1001
1002 39
        $ixfe = 0x8000; // Index to cell style XF
1003 39
        $BuiltIn = 0x00; // Built-in style
1004 39
        $iLevel = 0xff; // Outline style level
1005
1006 39
        $header = pack('vv', $record, $length);
1007 39
        $data = pack('vCC', $ixfe, $BuiltIn, $iLevel);
1008 39
        $this->append($header . $data);
1009 39
    }
1010
1011
    /**
1012
     * Writes Excel FORMAT record for non "built-in" numerical formats.
1013
     *
1014
     * @param string $format Custom format string
1015
     * @param int $ifmt Format index code
1016
     */
1017 14 View Code Duplication
    private function writeNumberFormat($format, $ifmt)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1018
    {
1019 14
        $record = 0x041E; // Record identifier
1020
1021 14
        $numberFormatString = StringHelper::UTF8toBIFF8UnicodeLong($format);
1022 14
        $length = 2 + strlen($numberFormatString); // Number of bytes to follow
1023
1024 14
        $header = pack('vv', $record, $length);
1025 14
        $data = pack('v', $ifmt) . $numberFormatString;
1026 14
        $this->append($header . $data);
1027 14
    }
1028
1029
    /**
1030
     * Write DATEMODE record to indicate the date system in use (1904 or 1900).
1031
     */
1032 39
    private function writeDateMode()
1033
    {
1034 39
        $record = 0x0022; // Record identifier
1035 39
        $length = 0x0002; // Bytes to follow
1036
1037 39
        $f1904 = (Date::getExcelCalendar() == Date::CALENDAR_MAC_1904)
1038
            ? 1
1039 39
            : 0; // Flag for 1904 date system
1040
1041 39
        $header = pack('vv', $record, $length);
1042 39
        $data = pack('v', $f1904);
1043 39
        $this->append($header . $data);
1044 39
    }
1045
1046
    /**
1047
     * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
1048
     * references in the workbook.
1049
     *
1050
     * Excel only stores references to external sheets that are used in NAME.
1051
     * The workbook NAME record is required to define the print area and the repeat
1052
     * rows and columns.
1053
     *
1054
     * A similar method is used in Worksheet.php for a slightly different purpose.
1055
     *
1056
     * @param int $cxals Number of external references
1057
     */
1058
    private function writeExternalCount($cxals)
1059
    {
1060
        $record = 0x0016; // Record identifier
1061
        $length = 0x0002; // Number of bytes to follow
1062
1063
        $header = pack('vv', $record, $length);
1064
        $data = pack('v', $cxals);
1065
        $this->append($header . $data);
1066
    }
1067
1068
    /**
1069
     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
1070
     * formulas. NAME record is required to define the print area and the repeat
1071
     * rows and columns.
1072
     *
1073
     * A similar method is used in Worksheet.php for a slightly different purpose.
1074
     *
1075
     * @param string $sheetname Worksheet name
1076
     */
1077 View Code Duplication
    private function writeExternalSheet($sheetname)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1078
    {
1079
        $record = 0x0017; // Record identifier
1080
        $length = 0x02 + strlen($sheetname); // Number of bytes to follow
1081
1082
        $cch = strlen($sheetname); // Length of sheet name
1083
        $rgch = 0x03; // Filename encoding
1084
1085
        $header = pack('vv', $record, $length);
1086
        $data = pack('CC', $cch, $rgch);
1087
        $this->append($header . $data . $sheetname);
1088
    }
1089
1090
    /**
1091
     * Store the NAME record in the short format that is used for storing the print
1092
     * area, repeat rows only and repeat columns only.
1093
     *
1094
     * @param int $index Sheet index
1095
     * @param int $type Built-in name type
1096
     * @param int $rowmin Start row
1097
     * @param int $rowmax End row
1098
     * @param int $colmin Start colum
1099
     * @param int $colmax End column
1100
     */
1101
    private function writeNameShort($index, $type, $rowmin, $rowmax, $colmin, $colmax)
1102
    {
1103
        $record = 0x0018; // Record identifier
1104
        $length = 0x0024; // Number of bytes to follow
1105
1106
        $grbit = 0x0020; // Option flags
1107
        $chKey = 0x00; // Keyboard shortcut
1108
        $cch = 0x01; // Length of text name
1109
        $cce = 0x0015; // Length of text definition
1110
        $ixals = $index + 1; // Sheet index
1111
        $itab = $ixals; // Equal to ixals
1112
        $cchCustMenu = 0x00; // Length of cust menu text
1113
        $cchDescription = 0x00; // Length of description text
1114
        $cchHelptopic = 0x00; // Length of help topic text
1115
        $cchStatustext = 0x00; // Length of status bar text
1116
        $rgch = $type; // Built-in name type
1117
1118
        $unknown03 = 0x3b;
1119
        $unknown04 = 0xffff - $index;
1120
        $unknown05 = 0x0000;
1121
        $unknown06 = 0x0000;
1122
        $unknown07 = 0x1087;
1123
        $unknown08 = 0x8005;
1124
1125
        $header = pack('vv', $record, $length);
1126
        $data = pack('v', $grbit);
1127
        $data .= pack('C', $chKey);
1128
        $data .= pack('C', $cch);
1129
        $data .= pack('v', $cce);
1130
        $data .= pack('v', $ixals);
1131
        $data .= pack('v', $itab);
1132
        $data .= pack('C', $cchCustMenu);
1133
        $data .= pack('C', $cchDescription);
1134
        $data .= pack('C', $cchHelptopic);
1135
        $data .= pack('C', $cchStatustext);
1136
        $data .= pack('C', $rgch);
1137
        $data .= pack('C', $unknown03);
1138
        $data .= pack('v', $unknown04);
1139
        $data .= pack('v', $unknown05);
1140
        $data .= pack('v', $unknown06);
1141
        $data .= pack('v', $unknown07);
1142
        $data .= pack('v', $unknown08);
1143
        $data .= pack('v', $index);
1144
        $data .= pack('v', $index);
1145
        $data .= pack('v', $rowmin);
1146
        $data .= pack('v', $rowmax);
1147
        $data .= pack('C', $colmin);
1148
        $data .= pack('C', $colmax);
1149
        $this->append($header . $data);
1150
    }
1151
1152
    /**
1153
     * Store the NAME record in the long format that is used for storing the repeat
1154
     * rows and columns when both are specified. This shares a lot of code with
1155
     * writeNameShort() but we use a separate method to keep the code clean.
1156
     * Code abstraction for reuse can be carried too far, and I should know. ;-).
1157
     *
1158
     * @param int $index Sheet index
1159
     * @param int $type Built-in name type
1160
     * @param int $rowmin Start row
1161
     * @param int $rowmax End row
1162
     * @param int $colmin Start colum
1163
     * @param int $colmax End column
1164
     */
1165
    private function writeNameLong($index, $type, $rowmin, $rowmax, $colmin, $colmax)
1166
    {
1167
        $record = 0x0018; // Record identifier
1168
        $length = 0x003d; // Number of bytes to follow
1169
        $grbit = 0x0020; // Option flags
1170
        $chKey = 0x00; // Keyboard shortcut
1171
        $cch = 0x01; // Length of text name
1172
        $cce = 0x002e; // Length of text definition
1173
        $ixals = $index + 1; // Sheet index
1174
        $itab = $ixals; // Equal to ixals
1175
        $cchCustMenu = 0x00; // Length of cust menu text
1176
        $cchDescription = 0x00; // Length of description text
1177
        $cchHelptopic = 0x00; // Length of help topic text
1178
        $cchStatustext = 0x00; // Length of status bar text
1179
        $rgch = $type; // Built-in name type
1180
1181
        $unknown01 = 0x29;
1182
        $unknown02 = 0x002b;
1183
        $unknown03 = 0x3b;
1184
        $unknown04 = 0xffff - $index;
1185
        $unknown05 = 0x0000;
1186
        $unknown06 = 0x0000;
1187
        $unknown07 = 0x1087;
1188
        $unknown08 = 0x8008;
1189
1190
        $header = pack('vv', $record, $length);
1191
        $data = pack('v', $grbit);
1192
        $data .= pack('C', $chKey);
1193
        $data .= pack('C', $cch);
1194
        $data .= pack('v', $cce);
1195
        $data .= pack('v', $ixals);
1196
        $data .= pack('v', $itab);
1197
        $data .= pack('C', $cchCustMenu);
1198
        $data .= pack('C', $cchDescription);
1199
        $data .= pack('C', $cchHelptopic);
1200
        $data .= pack('C', $cchStatustext);
1201
        $data .= pack('C', $rgch);
1202
        $data .= pack('C', $unknown01);
1203
        $data .= pack('v', $unknown02);
1204
        // Column definition
1205
        $data .= pack('C', $unknown03);
1206
        $data .= pack('v', $unknown04);
1207
        $data .= pack('v', $unknown05);
1208
        $data .= pack('v', $unknown06);
1209
        $data .= pack('v', $unknown07);
1210
        $data .= pack('v', $unknown08);
1211
        $data .= pack('v', $index);
1212
        $data .= pack('v', $index);
1213
        $data .= pack('v', 0x0000);
1214
        $data .= pack('v', 0x3fff);
1215
        $data .= pack('C', $colmin);
1216
        $data .= pack('C', $colmax);
1217
        // Row definition
1218
        $data .= pack('C', $unknown03);
1219
        $data .= pack('v', $unknown04);
1220
        $data .= pack('v', $unknown05);
1221
        $data .= pack('v', $unknown06);
1222
        $data .= pack('v', $unknown07);
1223
        $data .= pack('v', $unknown08);
1224
        $data .= pack('v', $index);
1225
        $data .= pack('v', $index);
1226
        $data .= pack('v', $rowmin);
1227
        $data .= pack('v', $rowmax);
1228
        $data .= pack('C', 0x00);
1229
        $data .= pack('C', 0xff);
1230
        // End of data
1231
        $data .= pack('C', 0x10);
1232
        $this->append($header . $data);
1233
    }
1234
1235
    /**
1236
     * Stores the COUNTRY record for localization.
1237
     *
1238
     * @return string
1239
     */
1240 View Code Duplication
    private function writeCountry()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1241
    {
1242
        $record = 0x008C; // Record identifier
1243
        $length = 4; // Number of bytes to follow
1244
1245
        $header = pack('vv', $record, $length);
1246
        /* using the same country code always for simplicity */
1247
        $data = pack('vv', $this->countryCode, $this->countryCode);
1248
1249
        return $this->writeData($header . $data);
1250
    }
1251
1252
    /**
1253
     * Write the RECALCID record.
1254
     *
1255
     * @return string
1256
     */
1257 39 View Code Duplication
    private function writeRecalcId()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1258
    {
1259 39
        $record = 0x01C1; // Record identifier
1260 39
        $length = 8; // Number of bytes to follow
1261
1262 39
        $header = pack('vv', $record, $length);
1263
1264
        // by inspection of real Excel files, MS Office Excel 2007 writes this
1265 39
        $data = pack('VV', 0x000001C1, 0x00001E667);
1266
1267 39
        return $this->writeData($header . $data);
1268
    }
1269
1270
    /**
1271
     * Stores the PALETTE biff record.
1272
     */
1273 39
    private function writePalette()
1274
    {
1275 39
        $aref = $this->palette;
1276
1277 39
        $record = 0x0092; // Record identifier
1278 39
        $length = 2 + 4 * count($aref); // Number of bytes to follow
1279 39
        $ccv = count($aref); // Number of RGB values to follow
1280 39
        $data = ''; // The RGB data
1281
1282
        // Pack the RGB data
1283 39
        foreach ($aref as $color) {
1284 39
            foreach ($color as $byte) {
1285 39
                $data .= pack('C', $byte);
1286
            }
1287
        }
1288
1289 39
        $header = pack('vvv', $record, $length, $ccv);
1290 39
        $this->append($header . $data);
1291 39
    }
1292
1293
    /**
1294
     * Handling of the SST continue blocks is complicated by the need to include an
1295
     * additional continuation byte depending on whether the string is split between
1296
     * blocks or whether it starts at the beginning of the block. (There are also
1297
     * additional complications that will arise later when/if Rich Strings are
1298
     * supported).
1299
     *
1300
     * The Excel documentation says that the SST record should be followed by an
1301
     * EXTSST record. The EXTSST record is a hash table that is used to optimise
1302
     * access to SST. However, despite the documentation it doesn't seem to be
1303
     * required so we will ignore it.
1304
     *
1305
     * @return string Binary data
1306
     */
1307 39
    private function writeSharedStringsTable()
1308
    {
1309
        // maximum size of record data (excluding record header)
1310 39
        $continue_limit = 8224;
1311
1312
        // initialize array of record data blocks
1313 39
        $recordDatas = [];
1314
1315
        // start SST record data block with total number of strings, total number of unique strings
1316 39
        $recordData = pack('VV', $this->stringTotal, $this->stringUnique);
1317
1318
        // loop through all (unique) strings in shared strings table
1319 39
        foreach (array_keys($this->stringTable) as $string) {
1320
            // here $string is a BIFF8 encoded string
1321
1322
            // length = character count
1323 35
            $headerinfo = unpack('vlength/Cencoding', $string);
1324
1325
            // currently, this is always 1 = uncompressed
1326 35
            $encoding = $headerinfo['encoding'];
1327
1328
            // initialize finished writing current $string
1329 35
            $finished = false;
1330
1331 35
            while ($finished === false) {
1332
                // normally, there will be only one cycle, but if string cannot immediately be written as is
1333
                // there will be need for more than one cylcle, if string longer than one record data block, there
1334
                // may be need for even more cycles
1335
1336 35
                if (strlen($recordData) + strlen($string) <= $continue_limit) {
1337
                    // then we can write the string (or remainder of string) without any problems
1338 35
                    $recordData .= $string;
1339
1340 35
                    if (strlen($recordData) + strlen($string) == $continue_limit) {
1341
                        // we close the record data block, and initialize a new one
1342
                        $recordDatas[] = $recordData;
1343
                        $recordData = '';
1344
                    }
1345
1346
                    // we are finished writing this string
1347 35
                    $finished = true;
1348
                } else {
1349
                    // special treatment writing the string (or remainder of the string)
1350
                    // If the string is very long it may need to be written in more than one CONTINUE record.
1351
1352
                    // check how many bytes more there is room for in the current record
1353
                    $space_remaining = $continue_limit - strlen($recordData);
1354
1355
                    // minimum space needed
1356
                    // uncompressed: 2 byte string length length field + 1 byte option flags + 2 byte character
1357
                    // compressed:   2 byte string length length field + 1 byte option flags + 1 byte character
1358
                    $min_space_needed = ($encoding == 1) ? 5 : 4;
1359
1360
                    // We have two cases
1361
                    // 1. space remaining is less than minimum space needed
1362
                    //        here we must waste the space remaining and move to next record data block
1363
                    // 2. space remaining is greater than or equal to minimum space needed
1364
                    //        here we write as much as we can in the current block, then move to next record data block
1365
1366
                    // 1. space remaining is less than minimum space needed
1367
                    if ($space_remaining < $min_space_needed) {
1368
                        // we close the block, store the block data
1369
                        $recordDatas[] = $recordData;
1370
1371
                        // and start new record data block where we start writing the string
1372
                        $recordData = '';
1373
1374
                    // 2. space remaining is greater than or equal to minimum space needed
1375
                    } else {
1376
                        // initialize effective remaining space, for Unicode strings this may need to be reduced by 1, see below
1377
                        $effective_space_remaining = $space_remaining;
1378
1379
                        // for uncompressed strings, sometimes effective space remaining is reduced by 1
1380
                        if ($encoding == 1 && (strlen($string) - $space_remaining) % 2 == 1) {
1381
                            --$effective_space_remaining;
1382
                        }
1383
1384
                        // one block fininshed, store the block data
1385
                        $recordData .= substr($string, 0, $effective_space_remaining);
1386
1387
                        $string = substr($string, $effective_space_remaining); // for next cycle in while loop
1388
                        $recordDatas[] = $recordData;
1389
1390
                        // start new record data block with the repeated option flags
1391
                        $recordData = pack('C', $encoding);
1392
                    }
1393
                }
1394
            }
1395
        }
1396
1397
        // Store the last record data block unless it is empty
1398
        // if there was no need for any continue records, this will be the for SST record data block itself
1399 39
        if (strlen($recordData) > 0) {
1400 39
            $recordDatas[] = $recordData;
1401
        }
1402
1403
        // combine into one chunk with all the blocks SST, CONTINUE,...
1404 39
        $chunk = '';
1405 39
        foreach ($recordDatas as $i => $recordData) {
1406
            // first block should have the SST record header, remaing should have CONTINUE header
1407 39
            $record = ($i == 0) ? 0x00FC : 0x003C;
1408
1409 39
            $header = pack('vv', $record, strlen($recordData));
1410 39
            $data = $header . $recordData;
1411
1412 39
            $chunk .= $this->writeData($data);
1413
        }
1414
1415 39
        return $chunk;
1416
    }
1417
1418
    /**
1419
     * Writes the MSODRAWINGGROUP record if needed. Possibly split using CONTINUE records.
1420
     */
1421 39
    private function writeMsoDrawingGroup()
1422
    {
1423
        // write the Escher stream if necessary
1424 39
        if (isset($this->escher)) {
1425 10
            $writer = new Escher($this->escher);
1426 10
            $data = $writer->close();
1427
1428 10
            $record = 0x00EB;
1429 10
            $length = strlen($data);
1430 10
            $header = pack('vv', $record, $length);
1431
1432 10
            return $this->writeData($header . $data);
1433
        }
1434
1435 29
        return '';
1436
    }
1437
1438
    /**
1439
     * Get Escher object.
1440
     *
1441
     * @return \PhpOffice\PhpSpreadsheet\Shared\Escher
1442
     */
1443
    public function getEscher()
1444
    {
1445
        return $this->escher;
1446
    }
1447
1448
    /**
1449
     * Set Escher object.
1450
     *
1451
     * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue
1452
     */
1453 10
    public function setEscher(\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null)
1454
    {
1455 10
        $this->escher = $pValue;
1456 10
    }
1457
}
1458