Completed
Push — develop ( c96e2d...d2f55f )
by Adrien
48:43 queued 44:11
created

Workbook::__construct()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 21
nc 3
nop 6
dl 0
loc 34
ccs 22
cts 22
cp 1
crap 3
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
4
5
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
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
use PhpOffice\PhpSpreadsheet\Style\Style;
11
12
// Original file header of PEAR::Spreadsheet_Excel_Writer_Workbook (used as the base for this class):
13
// -----------------------------------------------------------------------------------------
14
// /*
15
// *  Module written/ported by Xavier Noguer <[email protected]>
16
// *
17
// *  The majority of this is _NOT_ my code.  I simply ported it from the
18
// *  PERL Spreadsheet::WriteExcel module.
19
// *
20
// *  The author of the Spreadsheet::WriteExcel module is John McNamara
21
// *  <[email protected]>
22
// *
23
// *  I _DO_ maintain this code, and John McNamara has nothing to do with the
24
// *  porting of this code to PHP.  Any questions directly related to this
25
// *  class library should be directed to me.
26
// *
27
// *  License Information:
28
// *
29
// *    Spreadsheet_Excel_Writer:  A library for generating Excel Spreadsheets
30
// *    Copyright (c) 2002-2003 Xavier Noguer [email protected]
31
// *
32
// *    This library is free software; you can redistribute it and/or
33
// *    modify it under the terms of the GNU Lesser General Public
34
// *    License as published by the Free Software Foundation; either
35
// *    version 2.1 of the License, or (at your option) any later version.
36
// *
37
// *    This library is distributed in the hope that it will be useful,
38
// *    but WITHOUT ANY WARRANTY; without even the implied warranty of
39
// *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
40
// *    Lesser General Public License for more details.
41
// *
42
// *    You should have received a copy of the GNU Lesser General Public
43
// *    License along with this library; if not, write to the Free Software
44
// *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
45
// */
46
class Workbook extends BIFFwriter
47
{
48
    /**
49
     * Formula parser.
50
     *
51
     * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
52
     */
53
    private $parser;
54
55
    /**
56
     * The BIFF file size for the workbook.
57
     *
58
     * @var int
59
     *
60
     * @see calcSheetOffsets()
61
     */
62
    private $biffSize;
63
64
    /**
65
     * XF Writers.
66
     *
67
     * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Xf[]
68
     */
69
    private $xfWriters = [];
70
71
    /**
72
     * Array containing the colour palette.
73
     *
74
     * @var array
75
     */
76
    private $palette;
77
78
    /**
79
     * The codepage indicates the text encoding used for strings.
80
     *
81
     * @var int
82
     */
83
    private $codepage;
84
85
    /**
86
     * The country code used for localization.
87
     *
88
     * @var int
89
     */
90
    private $countryCode;
91
92
    /**
93
     * Workbook.
94
     *
95
     * @var Spreadsheet
96
     */
97
    private $spreadsheet;
98
99
    /**
100
     * Fonts writers.
101
     *
102
     * @var Font[]
103
     */
104
    private $fontWriters = [];
105
106
    /**
107
     * Added fonts. Maps from font's hash => index in workbook.
108
     *
109
     * @var array
110
     */
111
    private $addedFonts = [];
112
113
    /**
114
     * Shared number formats.
115
     *
116
     * @var array
117
     */
118
    private $numberFormats = [];
119
120
    /**
121
     * Added number formats. Maps from numberFormat's hash => index in workbook.
122
     *
123
     * @var array
124
     */
125
    private $addedNumberFormats = [];
126
127
    /**
128
     * Sizes of the binary worksheet streams.
129
     *
130
     * @var array
131
     */
132
    private $worksheetSizes = [];
133
134
    /**
135
     * Offsets of the binary worksheet streams relative to the start of the global workbook stream.
136
     *
137
     * @var array
138
     */
139
    private $worksheetOffsets = [];
140
141
    /**
142
     * Total number of shared strings in workbook.
143
     *
144
     * @var int
145
     */
146
    private $stringTotal;
147
148
    /**
149
     * Number of unique shared strings in workbook.
150
     *
151
     * @var int
152
     */
153
    private $stringUnique;
154
155
    /**
156
     * Array of unique shared strings in workbook.
157
     *
158
     * @var array
159
     */
160
    private $stringTable;
161
162
    /**
163
     * Color cache.
164
     */
165
    private $colors;
166
167
    /**
168
     * Escher object corresponding to MSODRAWINGGROUP.
169
     *
170
     * @var \PhpOffice\PhpSpreadsheet\Shared\Escher
171
     */
172
    private $escher;
173
174
    /**
175
     * Class constructor.
176
     *
177
     * @param Spreadsheet $spreadsheet The Workbook
178
     * @param int $str_total Total number of strings
179
     * @param int $str_unique Total number of unique strings
180
     * @param array $str_table String Table
181
     * @param array $colors Colour Table
182
     * @param Parser $parser The formula parser created for the Workbook
183
     */
184 51
    public function __construct(Spreadsheet $spreadsheet, &$str_total, &$str_unique, &$str_table, &$colors, Parser $parser)
185
    {
186
        // It needs to call its parent's constructor explicitly
187 51
        parent::__construct();
188
189 51
        $this->parser = $parser;
190 51
        $this->biffSize = 0;
191 51
        $this->palette = [];
192 51
        $this->countryCode = -1;
193
194 51
        $this->stringTotal = &$str_total;
195 51
        $this->stringUnique = &$str_unique;
196 51
        $this->stringTable = &$str_table;
197 51
        $this->colors = &$colors;
198 51
        $this->setPaletteXl97();
199
200 51
        $this->spreadsheet = $spreadsheet;
201
202 51
        $this->codepage = 0x04B0;
203
204
        // Add empty sheets and Build color cache
205 51
        $countSheets = $spreadsheet->getSheetCount();
206 51
        for ($i = 0; $i < $countSheets; ++$i) {
207 51
            $phpSheet = $spreadsheet->getSheet($i);
208
209 51
            $this->parser->setExtSheet($phpSheet->getTitle(), $i); // Register worksheet name with parser
210
211 51
            $supbook_index = 0x00;
212 51
            $ref = pack('vvv', $supbook_index, $i, $i);
213 51
            $this->parser->references[] = $ref; // Register reference with parser
214
215
            // Sheet tab colors?
216 51
            if ($phpSheet->isTabColorSet()) {
217 5
                $this->addColor($phpSheet->getTabColor()->getRGB());
218
            }
219
        }
220 51
    }
221
222
    /**
223
     * Add a new XF writer.
224
     *
225
     * @param Style $style
226
     * @param bool $isStyleXf Is it a style XF?
227
     *
228
     * @return int Index to XF record
229
     */
230 42
    public function addXfWriter(Style $style, $isStyleXf = false)
231
    {
232 42
        $xfWriter = new Xf($style);
233 42
        $xfWriter->setIsStyleXf($isStyleXf);
234
235
        // Add the font if not already added
236 42
        $fontIndex = $this->addFont($style->getFont());
237
238
        // Assign the font index to the xf record
239 42
        $xfWriter->setFontIndex($fontIndex);
240
241
        // Background colors, best to treat these after the font so black will come after white in custom palette
242 42
        $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB()));
243 42
        $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB()));
244 42
        $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB()));
245 42
        $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB()));
246 42
        $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB()));
247 42
        $xfWriter->setLeftColor($this->addColor($style->getBorders()->getLeft()->getColor()->getRGB()));
248 42
        $xfWriter->setDiagColor($this->addColor($style->getBorders()->getDiagonal()->getColor()->getRGB()));
249
250
        // Add the number format if it is not a built-in one and not already added
251 42
        if ($style->getNumberFormat()->getBuiltInFormatCode() === false) {
0 ignored issues
show
introduced by
The condition $style->getNumberFormat(...nFormatCode() === false can never be true.
Loading history...
252 14
            $numberFormatHashCode = $style->getNumberFormat()->getHashCode();
253
254 14
            if (isset($this->addedNumberFormats[$numberFormatHashCode])) {
255 6
                $numberFormatIndex = $this->addedNumberFormats[$numberFormatHashCode];
256
            } else {
257 14
                $numberFormatIndex = 164 + count($this->numberFormats);
258 14
                $this->numberFormats[$numberFormatIndex] = $style->getNumberFormat();
259 14
                $this->addedNumberFormats[$numberFormatHashCode] = $numberFormatIndex;
260
            }
261
        } else {
262 42
            $numberFormatIndex = (int) $style->getNumberFormat()->getBuiltInFormatCode();
263
        }
264
265
        // Assign the number format index to xf record
266 42
        $xfWriter->setNumberFormatIndex($numberFormatIndex);
267
268 42
        $this->xfWriters[] = $xfWriter;
269
270 42
        $xfIndex = count($this->xfWriters) - 1;
271
272 42
        return $xfIndex;
273
    }
274
275
    /**
276
     * Add a font to added fonts.
277
     *
278
     * @param \PhpOffice\PhpSpreadsheet\Style\Font $font
279
     *
280
     * @return int Index to FONT record
281
     */
282 42
    public function addFont(\PhpOffice\PhpSpreadsheet\Style\Font $font)
283
    {
284 42
        $fontHashCode = $font->getHashCode();
285 42
        if (isset($this->addedFonts[$fontHashCode])) {
286 42
            $fontIndex = $this->addedFonts[$fontHashCode];
287
        } else {
288 42
            $countFonts = count($this->fontWriters);
289 42
            $fontIndex = ($countFonts < 4) ? $countFonts : $countFonts + 1;
290
291 42
            $fontWriter = new Font($font);
292 42
            $fontWriter->setColorIndex($this->addColor($font->getColor()->getRGB()));
293 42
            $this->fontWriters[] = $fontWriter;
294
295 42
            $this->addedFonts[$fontHashCode] = $fontIndex;
296
        }
297
298 42
        return $fontIndex;
299
    }
300
301
    /**
302
     * Alter color palette adding a custom color.
303
     *
304
     * @param string $rgb E.g. 'FF00AA'
305
     *
306
     * @return int Color index
307
     */
308 51
    private function addColor($rgb)
309
    {
310 51
        if (!isset($this->colors[$rgb])) {
311
            $color =
312
                [
313 51
                    hexdec(substr($rgb, 0, 2)),
314 51
                    hexdec(substr($rgb, 2, 2)),
315 51
                    hexdec(substr($rgb, 4)),
316 51
                    0,
317
                ];
318 51
            $colorIndex = array_search($color, $this->palette);
319 51
            if ($colorIndex) {
320 48
                $this->colors[$rgb] = $colorIndex;
321
            } else {
322 17
                if (count($this->colors) == 0) {
323 9
                    $lastColor = 7;
324
                } else {
325 15
                    $lastColor = end($this->colors);
326
                }
327 17
                if ($lastColor < 57) {
328
                    // then we add a custom color altering the palette
329 15
                    $colorIndex = $lastColor + 1;
330 15
                    $this->palette[$colorIndex] = $color;
331 15
                    $this->colors[$rgb] = $colorIndex;
332
                } else {
333
                    // no room for more custom colors, just map to black
334 51
                    $colorIndex = 0;
335
                }
336
            }
337
        } else {
338
            // fetch already added custom color
339 44
            $colorIndex = $this->colors[$rgb];
340
        }
341
342 51
        return $colorIndex;
343
    }
344
345
    /**
346
     * Sets the colour palette to the Excel 97+ default.
347
     */
348 51
    private function setPaletteXl97()
349
    {
350 51
        $this->palette = [
351
            0x08 => [0x00, 0x00, 0x00, 0x00],
352
            0x09 => [0xff, 0xff, 0xff, 0x00],
353
            0x0A => [0xff, 0x00, 0x00, 0x00],
354
            0x0B => [0x00, 0xff, 0x00, 0x00],
355
            0x0C => [0x00, 0x00, 0xff, 0x00],
356
            0x0D => [0xff, 0xff, 0x00, 0x00],
357
            0x0E => [0xff, 0x00, 0xff, 0x00],
358
            0x0F => [0x00, 0xff, 0xff, 0x00],
359
            0x10 => [0x80, 0x00, 0x00, 0x00],
360
            0x11 => [0x00, 0x80, 0x00, 0x00],
361
            0x12 => [0x00, 0x00, 0x80, 0x00],
362
            0x13 => [0x80, 0x80, 0x00, 0x00],
363
            0x14 => [0x80, 0x00, 0x80, 0x00],
364
            0x15 => [0x00, 0x80, 0x80, 0x00],
365
            0x16 => [0xc0, 0xc0, 0xc0, 0x00],
366
            0x17 => [0x80, 0x80, 0x80, 0x00],
367
            0x18 => [0x99, 0x99, 0xff, 0x00],
368
            0x19 => [0x99, 0x33, 0x66, 0x00],
369
            0x1A => [0xff, 0xff, 0xcc, 0x00],
370
            0x1B => [0xcc, 0xff, 0xff, 0x00],
371
            0x1C => [0x66, 0x00, 0x66, 0x00],
372
            0x1D => [0xff, 0x80, 0x80, 0x00],
373
            0x1E => [0x00, 0x66, 0xcc, 0x00],
374
            0x1F => [0xcc, 0xcc, 0xff, 0x00],
375
            0x20 => [0x00, 0x00, 0x80, 0x00],
376
            0x21 => [0xff, 0x00, 0xff, 0x00],
377
            0x22 => [0xff, 0xff, 0x00, 0x00],
378
            0x23 => [0x00, 0xff, 0xff, 0x00],
379
            0x24 => [0x80, 0x00, 0x80, 0x00],
380
            0x25 => [0x80, 0x00, 0x00, 0x00],
381
            0x26 => [0x00, 0x80, 0x80, 0x00],
382
            0x27 => [0x00, 0x00, 0xff, 0x00],
383
            0x28 => [0x00, 0xcc, 0xff, 0x00],
384
            0x29 => [0xcc, 0xff, 0xff, 0x00],
385
            0x2A => [0xcc, 0xff, 0xcc, 0x00],
386
            0x2B => [0xff, 0xff, 0x99, 0x00],
387
            0x2C => [0x99, 0xcc, 0xff, 0x00],
388
            0x2D => [0xff, 0x99, 0xcc, 0x00],
389
            0x2E => [0xcc, 0x99, 0xff, 0x00],
390
            0x2F => [0xff, 0xcc, 0x99, 0x00],
391
            0x30 => [0x33, 0x66, 0xff, 0x00],
392
            0x31 => [0x33, 0xcc, 0xcc, 0x00],
393
            0x32 => [0x99, 0xcc, 0x00, 0x00],
394
            0x33 => [0xff, 0xcc, 0x00, 0x00],
395
            0x34 => [0xff, 0x99, 0x00, 0x00],
396
            0x35 => [0xff, 0x66, 0x00, 0x00],
397
            0x36 => [0x66, 0x66, 0x99, 0x00],
398
            0x37 => [0x96, 0x96, 0x96, 0x00],
399
            0x38 => [0x00, 0x33, 0x66, 0x00],
400
            0x39 => [0x33, 0x99, 0x66, 0x00],
401
            0x3A => [0x00, 0x33, 0x00, 0x00],
402
            0x3B => [0x33, 0x33, 0x00, 0x00],
403
            0x3C => [0x99, 0x33, 0x00, 0x00],
404
            0x3D => [0x99, 0x33, 0x66, 0x00],
405
            0x3E => [0x33, 0x33, 0x99, 0x00],
406
            0x3F => [0x33, 0x33, 0x33, 0x00],
407
        ];
408 51
    }
409
410
    /**
411
     * Assemble worksheets into a workbook and send the BIFF data to an OLE
412
     * storage.
413
     *
414
     * @param array $pWorksheetSizes The sizes in bytes of the binary worksheet streams
415
     *
416
     * @return string Binary data for workbook stream
417
     */
418 42
    public function writeWorkbook(array $pWorksheetSizes)
419
    {
420 42
        $this->worksheetSizes = $pWorksheetSizes;
421
422
        // Calculate the number of selected worksheet tabs and call the finalization
423
        // methods for each worksheet
424 42
        $total_worksheets = $this->spreadsheet->getSheetCount();
425
426
        // Add part 1 of the Workbook globals, what goes before the SHEET records
427 42
        $this->storeBof(0x0005);
428 42
        $this->writeCodepage();
429 42
        $this->writeWindow1();
430
431 42
        $this->writeDateMode();
432 42
        $this->writeAllFonts();
433 42
        $this->writeAllNumberFormats();
434 42
        $this->writeAllXfs();
435 42
        $this->writeAllStyles();
436 42
        $this->writePalette();
437
438
        // Prepare part 3 of the workbook global stream, what goes after the SHEET records
439 42
        $part3 = '';
440 42
        if ($this->countryCode != -1) {
441
            $part3 .= $this->writeCountry();
442
        }
443 42
        $part3 .= $this->writeRecalcId();
444
445 42
        $part3 .= $this->writeSupbookInternal();
446
        /* TODO: store external SUPBOOK records and XCT and CRN records
447
        in case of external references for BIFF8 */
448 42
        $part3 .= $this->writeExternalsheetBiff8();
449 42
        $part3 .= $this->writeAllDefinedNamesBiff8();
450 42
        $part3 .= $this->writeMsoDrawingGroup();
451 42
        $part3 .= $this->writeSharedStringsTable();
452
453 42
        $part3 .= $this->writeEof();
454
455
        // Add part 2 of the Workbook globals, the SHEET records
456 42
        $this->calcSheetOffsets();
457 42
        for ($i = 0; $i < $total_worksheets; ++$i) {
458 42
            $this->writeBoundSheet($this->spreadsheet->getSheet($i), $this->worksheetOffsets[$i]);
0 ignored issues
show
Bug introduced by
$this->spreadsheet->getSheet($i) of type PhpOffice\PhpSpreadsheet\Worksheet\Worksheet is incompatible with the type PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet expected by parameter $sheet of PhpOffice\PhpSpreadsheet...book::writeBoundSheet(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

458
            $this->writeBoundSheet(/** @scrutinizer ignore-type */ $this->spreadsheet->getSheet($i), $this->worksheetOffsets[$i]);
Loading history...
459
        }
460
461
        // Add part 3 of the Workbook globals
462 42
        $this->_data .= $part3;
463
464 42
        return $this->_data;
465
    }
466
467
    /**
468
     * Calculate offsets for Worksheet BOF records.
469
     */
470 42
    private function calcSheetOffsets()
471
    {
472 42
        $boundsheet_length = 10; // fixed length for a BOUNDSHEET record
473
474
        // size of Workbook globals part 1 + 3
475 42
        $offset = $this->_datasize;
476
477
        // add size of Workbook globals part 2, the length of the SHEET records
478 42
        $total_worksheets = count($this->spreadsheet->getAllSheets());
479 42
        foreach ($this->spreadsheet->getWorksheetIterator() as $sheet) {
480 42
            $offset += $boundsheet_length + strlen(StringHelper::UTF8toBIFF8UnicodeShort($sheet->getTitle()));
481
        }
482
483
        // add the sizes of each of the Sheet substreams, respectively
484 42
        for ($i = 0; $i < $total_worksheets; ++$i) {
485 42
            $this->worksheetOffsets[$i] = $offset;
486 42
            $offset += $this->worksheetSizes[$i];
487
        }
488 42
        $this->biffSize = $offset;
489 42
    }
490
491
    /**
492
     * Store the Excel FONT records.
493
     */
494 42
    private function writeAllFonts()
495
    {
496 42
        foreach ($this->fontWriters as $fontWriter) {
497 42
            $this->append($fontWriter->writeFont());
498
        }
499 42
    }
500
501
    /**
502
     * Store user defined numerical formats i.e. FORMAT records.
503
     */
504 42
    private function writeAllNumberFormats()
505
    {
506 42
        foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) {
507 14
            $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex);
508
        }
509 42
    }
510
511
    /**
512
     * Write all XF records.
513
     */
514 42
    private function writeAllXfs()
515
    {
516 42
        foreach ($this->xfWriters as $xfWriter) {
517 42
            $this->append($xfWriter->writeXf());
518
        }
519 42
    }
520
521
    /**
522
     * Write all STYLE records.
523
     */
524 42
    private function writeAllStyles()
525
    {
526 42
        $this->writeStyle();
527 42
    }
528
529
    /**
530
     * Writes all the DEFINEDNAME records (BIFF8).
531
     * So far this is only used for repeating rows/columns (print titles) and print areas.
532
     */
533 42
    private function writeAllDefinedNamesBiff8()
534
    {
535 42
        $chunk = '';
536
537
        // Named ranges
538 42
        if (count($this->spreadsheet->getNamedRanges()) > 0) {
539
            // Loop named ranges
540 4
            $namedRanges = $this->spreadsheet->getNamedRanges();
541 4
            foreach ($namedRanges as $namedRange) {
542
                // Create absolute coordinate
543 4
                $range = Coordinate::splitRange($namedRange->getRange());
544 4
                $iMax = count($range);
545 4
                for ($i = 0; $i < $iMax; ++$i) {
546 4
                    $range[$i][0] = '\'' . str_replace("'", "''", $namedRange->getWorksheet()->getTitle()) . '\'!' . Coordinate::absoluteCoordinate($range[$i][0]);
547 4
                    if (isset($range[$i][1])) {
548 3
                        $range[$i][1] = Coordinate::absoluteCoordinate($range[$i][1]);
549
                    }
550
                }
551 4
                $range = Coordinate::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...
552
553
                // parse formula
554
                try {
555 4
                    $error = $this->parser->parse($range);
0 ignored issues
show
Unused Code introduced by
The assignment to $error is dead and can be removed.
Loading history...
556 4
                    $formulaData = $this->parser->toReversePolish();
557
558
                    // make sure tRef3d is of type tRef3dR (0x3A)
559 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...
560 1
                        $formulaData = "\x3A" . substr($formulaData, 1);
561
                    }
562
563 4
                    if ($namedRange->getLocalOnly()) {
564
                        // local scope
565
                        $scope = $this->spreadsheet->getIndex($namedRange->getScope()) + 1;
566
                    } else {
567
                        // global scope
568 4
                        $scope = 0;
569
                    }
570 4
                    $chunk .= $this->writeData($this->writeDefinedNameBiff8($namedRange->getName(), $formulaData, $scope, false));
571 4
                } catch (PhpSpreadsheetException $e) {
572
                    // do nothing
573
                }
574
            }
575
        }
576
577
        // total number of sheets
578 42
        $total_worksheets = $this->spreadsheet->getSheetCount();
579
580
        // write the print titles (repeating rows, columns), if any
581 42
        for ($i = 0; $i < $total_worksheets; ++$i) {
582 42
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
583
            // simultaneous repeatColumns repeatRows
584 42
            if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
585
                $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
586
                $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1;
587
                $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1;
588
589
                $repeat = $sheetSetup->getRowsToRepeatAtTop();
590
                $rowmin = $repeat[0] - 1;
591
                $rowmax = $repeat[1] - 1;
592
593
                // construct formula data manually
594
                $formulaData = pack('Cv', 0x29, 0x17); // tMemFunc
595
                $formulaData .= pack('Cvvvvv', 0x3B, $i, 0, 65535, $colmin, $colmax); // tArea3d
596
                $formulaData .= pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, 0, 255); // tArea3d
597
                $formulaData .= pack('C', 0x10); // tList
598
599
                // store the DEFINEDNAME record
600
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
601
602
            // (exclusive) either repeatColumns or repeatRows
603 42
            } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
604
                // Columns to repeat
605 1
                if ($sheetSetup->isColumnsToRepeatAtLeftSet()) {
606
                    $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
607
                    $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1;
608
                    $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1;
609
                } else {
610 1
                    $colmin = 0;
611 1
                    $colmax = 255;
612
                }
613
                // Rows to repeat
614 1
                if ($sheetSetup->isRowsToRepeatAtTopSet()) {
615 1
                    $repeat = $sheetSetup->getRowsToRepeatAtTop();
616 1
                    $rowmin = $repeat[0] - 1;
617 1
                    $rowmax = $repeat[1] - 1;
618
                } else {
619
                    $rowmin = 0;
620
                    $rowmax = 65535;
621
                }
622
623
                // construct formula data manually because parser does not recognize absolute 3d cell references
624 1
                $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax);
625
626
                // store the DEFINEDNAME record
627 1
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
628
            }
629
        }
630
631
        // write the print areas, if any
632 42
        for ($i = 0; $i < $total_worksheets; ++$i) {
633 42
            $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
634 42
            if ($sheetSetup->isPrintAreaSet()) {
635
                // Print area, e.g. A3:J6,H1:X20
636
                $printArea = Coordinate::splitRange($sheetSetup->getPrintArea());
637
                $countPrintArea = count($printArea);
638
639
                $formulaData = '';
640
                for ($j = 0; $j < $countPrintArea; ++$j) {
641
                    $printAreaRect = $printArea[$j]; // e.g. A3:J6
642
                    $printAreaRect[0] = Coordinate::coordinateFromString($printAreaRect[0]);
643
                    $printAreaRect[1] = Coordinate::coordinateFromString($printAreaRect[1]);
644
645
                    $print_rowmin = $printAreaRect[0][1] - 1;
646
                    $print_rowmax = $printAreaRect[1][1] - 1;
647
                    $print_colmin = Coordinate::columnIndexFromString($printAreaRect[0][0]) - 1;
648
                    $print_colmax = Coordinate::columnIndexFromString($printAreaRect[1][0]) - 1;
649
650
                    // construct formula data manually because parser does not recognize absolute 3d cell references
651
                    $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax);
652
653
                    if ($j > 0) {
654
                        $formulaData .= pack('C', 0x10); // list operator token ','
655
                    }
656
                }
657
658
                // store the DEFINEDNAME record
659
                $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x06), $formulaData, $i + 1, true));
660
            }
661
        }
662
663
        // write autofilters, if any
664 42
        for ($i = 0; $i < $total_worksheets; ++$i) {
665 42
            $sheetAutoFilter = $this->spreadsheet->getSheet($i)->getAutoFilter();
666 42
            $autoFilterRange = $sheetAutoFilter->getRange();
667 42
            if (!empty($autoFilterRange)) {
668 3
                $rangeBounds = Coordinate::rangeBoundaries($autoFilterRange);
669
670
                //Autofilter built in name
671 3
                $name = pack('C', 0x0D);
672
673 3
                $chunk .= $this->writeData($this->writeShortNameBiff8($name, $i + 1, $rangeBounds, true));
674
            }
675
        }
676
677 42
        return $chunk;
678
    }
679
680
    /**
681
     * Write a DEFINEDNAME record for BIFF8 using explicit binary formula data.
682
     *
683
     * @param string $name The name in UTF-8
684
     * @param string $formulaData The binary formula data
685
     * @param int $sheetIndex 1-based sheet index the defined name applies to. 0 = global
686
     * @param bool $isBuiltIn Built-in name?
687
     *
688
     * @return string Complete binary record data
689
     */
690 5
    private function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false)
691
    {
692 5
        $record = 0x0018;
693
694
        // option flags
695 5
        $options = $isBuiltIn ? 0x20 : 0x00;
696
697
        // length of the name, character count
698 5
        $nlen = StringHelper::countCharacters($name);
699
700
        // name with stripped length field
701 5
        $name = substr(StringHelper::UTF8toBIFF8UnicodeLong($name), 2);
702
703
        // size of the formula (in bytes)
704 5
        $sz = strlen($formulaData);
705
706
        // combine the parts
707 5
        $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0)
708 5
            . $name . $formulaData;
709 5
        $length = strlen($data);
710
711 5
        $header = pack('vv', $record, $length);
712
713 5
        return $header . $data;
714
    }
715
716
    /**
717
     * Write a short NAME record.
718
     *
719
     * @param string $name
720
     * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global
721
     * @param integer[][] $rangeBounds range boundaries
722
     * @param bool $isHidden
723
     *
724
     * @return string Complete binary record data
725
     * */
726 3
    private function writeShortNameBiff8($name, $sheetIndex, $rangeBounds, $isHidden = false)
727
    {
728 3
        $record = 0x0018;
729
730
        // option flags
731 3
        $options = ($isHidden ? 0x21 : 0x00);
732
733 3
        $extra = pack(
734 3
            'Cvvvvv',
735 3
            0x3B,
736 3
            $sheetIndex - 1,
737 3
            $rangeBounds[0][1] - 1,
738 3
            $rangeBounds[1][1] - 1,
739 3
            $rangeBounds[0][0] - 1,
740 3
            $rangeBounds[1][0] - 1
741
        );
742
743
        // size of the formula (in bytes)
744 3
        $sz = strlen($extra);
745
746
        // combine the parts
747 3
        $data = pack('vCCvvvCCCCC', $options, 0, 1, $sz, 0, $sheetIndex, 0, 0, 0, 0, 0)
748 3
            . $name . $extra;
749 3
        $length = strlen($data);
750
751 3
        $header = pack('vv', $record, $length);
752
753 3
        return $header . $data;
754
    }
755
756
    /**
757
     * Stores the CODEPAGE biff record.
758
     */
759 42
    private function writeCodepage()
760
    {
761 42
        $record = 0x0042; // Record identifier
762 42
        $length = 0x0002; // Number of bytes to follow
763 42
        $cv = $this->codepage; // The code page
764
765 42
        $header = pack('vv', $record, $length);
766 42
        $data = pack('v', $cv);
767
768 42
        $this->append($header . $data);
769 42
    }
770
771
    /**
772
     * Write Excel BIFF WINDOW1 record.
773
     */
774 42
    private function writeWindow1()
775
    {
776 42
        $record = 0x003D; // Record identifier
777 42
        $length = 0x0012; // Number of bytes to follow
778
779 42
        $xWn = 0x0000; // Horizontal position of window
780 42
        $yWn = 0x0000; // Vertical position of window
781 42
        $dxWn = 0x25BC; // Width of window
782 42
        $dyWn = 0x1572; // Height of window
783
784 42
        $grbit = 0x0038; // Option flags
785
786
        // not supported by PhpSpreadsheet, so there is only one selected sheet, the active
787 42
        $ctabsel = 1; // Number of workbook tabs selected
788
789 42
        $wTabRatio = 0x0258; // Tab to scrollbar ratio
790
791
        // not supported by PhpSpreadsheet, set to 0
792 42
        $itabFirst = 0; // 1st displayed worksheet
793 42
        $itabCur = $this->spreadsheet->getActiveSheetIndex(); // Active worksheet
794
795 42
        $header = pack('vv', $record, $length);
796 42
        $data = pack('vvvvvvvvv', $xWn, $yWn, $dxWn, $dyWn, $grbit, $itabCur, $itabFirst, $ctabsel, $wTabRatio);
797 42
        $this->append($header . $data);
798 42
    }
799
800
    /**
801
     * Writes Excel BIFF BOUNDSHEET record.
802
     *
803
     * @param Worksheet $sheet Worksheet name
804
     * @param int $offset Location of worksheet BOF
805
     */
806 42
    private function writeBoundSheet($sheet, $offset)
807
    {
808 42
        $sheetname = $sheet->getTitle();
0 ignored issues
show
Bug introduced by
The method getTitle() does not exist on PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

808
        /** @scrutinizer ignore-call */ 
809
        $sheetname = $sheet->getTitle();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
809 42
        $record = 0x0085; // Record identifier
810
811
        // sheet state
812 42
        switch ($sheet->getSheetState()) {
0 ignored issues
show
Bug introduced by
The method getSheetState() does not exist on PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

812
        switch ($sheet->/** @scrutinizer ignore-call */ getSheetState()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
813 42
            case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VISIBLE:
814 42
                $ss = 0x00;
815
816 42
                break;
817
            case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_HIDDEN:
818
                $ss = 0x01;
819
820
                break;
821
            case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VERYHIDDEN:
822
                $ss = 0x02;
823
824
                break;
825
            default:
826
                $ss = 0x00;
827
828
                break;
829
        }
830
831
        // sheet type
832 42
        $st = 0x00;
833
834 42
        $grbit = 0x0000; // Visibility and sheet type
0 ignored issues
show
Unused Code introduced by
The assignment to $grbit is dead and can be removed.
Loading history...
835
836 42
        $data = pack('VCC', $offset, $ss, $st);
837 42
        $data .= StringHelper::UTF8toBIFF8UnicodeShort($sheetname);
838
839 42
        $length = strlen($data);
840 42
        $header = pack('vv', $record, $length);
841 42
        $this->append($header . $data);
842 42
    }
843
844
    /**
845
     * Write Internal SUPBOOK record.
846
     */
847 42
    private function writeSupbookInternal()
848
    {
849 42
        $record = 0x01AE; // Record identifier
850 42
        $length = 0x0004; // Bytes to follow
851
852 42
        $header = pack('vv', $record, $length);
853 42
        $data = pack('vv', $this->spreadsheet->getSheetCount(), 0x0401);
854
855 42
        return $this->writeData($header . $data);
856
    }
857
858
    /**
859
     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
860
     * formulas.
861
     */
862 42
    private function writeExternalsheetBiff8()
863
    {
864 42
        $totalReferences = count($this->parser->references);
865 42
        $record = 0x0017; // Record identifier
866 42
        $length = 2 + 6 * $totalReferences; // Number of bytes to follow
867
868 42
        $supbook_index = 0; // FIXME: only using internal SUPBOOK record
0 ignored issues
show
Unused Code introduced by
The assignment to $supbook_index is dead and can be removed.
Loading history...
869 42
        $header = pack('vv', $record, $length);
870 42
        $data = pack('v', $totalReferences);
871 42
        for ($i = 0; $i < $totalReferences; ++$i) {
872 42
            $data .= $this->parser->references[$i];
873
        }
874
875 42
        return $this->writeData($header . $data);
876
    }
877
878
    /**
879
     * Write Excel BIFF STYLE records.
880
     */
881 42
    private function writeStyle()
882
    {
883 42
        $record = 0x0293; // Record identifier
884 42
        $length = 0x0004; // Bytes to follow
885
886 42
        $ixfe = 0x8000; // Index to cell style XF
887 42
        $BuiltIn = 0x00; // Built-in style
888 42
        $iLevel = 0xff; // Outline style level
889
890 42
        $header = pack('vv', $record, $length);
891 42
        $data = pack('vCC', $ixfe, $BuiltIn, $iLevel);
892 42
        $this->append($header . $data);
893 42
    }
894
895
    /**
896
     * Writes Excel FORMAT record for non "built-in" numerical formats.
897
     *
898
     * @param string $format Custom format string
899
     * @param int $ifmt Format index code
900
     */
901 14
    private function writeNumberFormat($format, $ifmt)
902
    {
903 14
        $record = 0x041E; // Record identifier
904
905 14
        $numberFormatString = StringHelper::UTF8toBIFF8UnicodeLong($format);
906 14
        $length = 2 + strlen($numberFormatString); // Number of bytes to follow
907
908 14
        $header = pack('vv', $record, $length);
909 14
        $data = pack('v', $ifmt) . $numberFormatString;
910 14
        $this->append($header . $data);
911 14
    }
912
913
    /**
914
     * Write DATEMODE record to indicate the date system in use (1904 or 1900).
915
     */
916 42
    private function writeDateMode()
917
    {
918 42
        $record = 0x0022; // Record identifier
919 42
        $length = 0x0002; // Bytes to follow
920
921 42
        $f1904 = (Date::getExcelCalendar() == Date::CALENDAR_MAC_1904)
922
            ? 1
923 42
            : 0; // Flag for 1904 date system
924
925 42
        $header = pack('vv', $record, $length);
926 42
        $data = pack('v', $f1904);
927 42
        $this->append($header . $data);
928 42
    }
929
930
    /**
931
     * Stores the COUNTRY record for localization.
932
     *
933
     * @return string
934
     */
935
    private function writeCountry()
936
    {
937
        $record = 0x008C; // Record identifier
938
        $length = 4; // Number of bytes to follow
939
940
        $header = pack('vv', $record, $length);
941
        // using the same country code always for simplicity
942
        $data = pack('vv', $this->countryCode, $this->countryCode);
943
944
        return $this->writeData($header . $data);
945
    }
946
947
    /**
948
     * Write the RECALCID record.
949
     *
950
     * @return string
951
     */
952 42
    private function writeRecalcId()
953
    {
954 42
        $record = 0x01C1; // Record identifier
955 42
        $length = 8; // Number of bytes to follow
956
957 42
        $header = pack('vv', $record, $length);
958
959
        // by inspection of real Excel files, MS Office Excel 2007 writes this
960 42
        $data = pack('VV', 0x000001C1, 0x00001E667);
961
962 42
        return $this->writeData($header . $data);
963
    }
964
965
    /**
966
     * Stores the PALETTE biff record.
967
     */
968 42
    private function writePalette()
969
    {
970 42
        $aref = $this->palette;
971
972 42
        $record = 0x0092; // Record identifier
973 42
        $length = 2 + 4 * count($aref); // Number of bytes to follow
974 42
        $ccv = count($aref); // Number of RGB values to follow
975 42
        $data = ''; // The RGB data
976
977
        // Pack the RGB data
978 42
        foreach ($aref as $color) {
979 42
            foreach ($color as $byte) {
980 42
                $data .= pack('C', $byte);
981
            }
982
        }
983
984 42
        $header = pack('vvv', $record, $length, $ccv);
985 42
        $this->append($header . $data);
986 42
    }
987
988
    /**
989
     * Handling of the SST continue blocks is complicated by the need to include an
990
     * additional continuation byte depending on whether the string is split between
991
     * blocks or whether it starts at the beginning of the block. (There are also
992
     * additional complications that will arise later when/if Rich Strings are
993
     * supported).
994
     *
995
     * The Excel documentation says that the SST record should be followed by an
996
     * EXTSST record. The EXTSST record is a hash table that is used to optimise
997
     * access to SST. However, despite the documentation it doesn't seem to be
998
     * required so we will ignore it.
999
     *
1000
     * @return string Binary data
1001
     */
1002 42
    private function writeSharedStringsTable()
1003
    {
1004
        // maximum size of record data (excluding record header)
1005 42
        $continue_limit = 8224;
1006
1007
        // initialize array of record data blocks
1008 42
        $recordDatas = [];
1009
1010
        // start SST record data block with total number of strings, total number of unique strings
1011 42
        $recordData = pack('VV', $this->stringTotal, $this->stringUnique);
1012
1013
        // loop through all (unique) strings in shared strings table
1014 42
        foreach (array_keys($this->stringTable) as $string) {
1015
            // here $string is a BIFF8 encoded string
1016
1017
            // length = character count
1018 36
            $headerinfo = unpack('vlength/Cencoding', $string);
1019
1020
            // currently, this is always 1 = uncompressed
1021 36
            $encoding = $headerinfo['encoding'];
1022
1023
            // initialize finished writing current $string
1024 36
            $finished = false;
1025
1026 36
            while ($finished === false) {
1027
                // normally, there will be only one cycle, but if string cannot immediately be written as is
1028
                // there will be need for more than one cylcle, if string longer than one record data block, there
1029
                // may be need for even more cycles
1030
1031 36
                if (strlen($recordData) + strlen($string) <= $continue_limit) {
1032
                    // then we can write the string (or remainder of string) without any problems
1033 36
                    $recordData .= $string;
1034
1035 36
                    if (strlen($recordData) + strlen($string) == $continue_limit) {
1036
                        // we close the record data block, and initialize a new one
1037
                        $recordDatas[] = $recordData;
1038
                        $recordData = '';
1039
                    }
1040
1041
                    // we are finished writing this string
1042 36
                    $finished = true;
1043
                } else {
1044
                    // special treatment writing the string (or remainder of the string)
1045
                    // If the string is very long it may need to be written in more than one CONTINUE record.
1046
1047
                    // check how many bytes more there is room for in the current record
1048
                    $space_remaining = $continue_limit - strlen($recordData);
1049
1050
                    // minimum space needed
1051
                    // uncompressed: 2 byte string length length field + 1 byte option flags + 2 byte character
1052
                    // compressed:   2 byte string length length field + 1 byte option flags + 1 byte character
1053
                    $min_space_needed = ($encoding == 1) ? 5 : 4;
1054
1055
                    // We have two cases
1056
                    // 1. space remaining is less than minimum space needed
1057
                    //        here we must waste the space remaining and move to next record data block
1058
                    // 2. space remaining is greater than or equal to minimum space needed
1059
                    //        here we write as much as we can in the current block, then move to next record data block
1060
1061
                    // 1. space remaining is less than minimum space needed
1062
                    if ($space_remaining < $min_space_needed) {
1063
                        // we close the block, store the block data
1064
                        $recordDatas[] = $recordData;
1065
1066
                        // and start new record data block where we start writing the string
1067
                        $recordData = '';
1068
1069
                    // 2. space remaining is greater than or equal to minimum space needed
1070
                    } else {
1071
                        // initialize effective remaining space, for Unicode strings this may need to be reduced by 1, see below
1072
                        $effective_space_remaining = $space_remaining;
1073
1074
                        // for uncompressed strings, sometimes effective space remaining is reduced by 1
1075
                        if ($encoding == 1 && (strlen($string) - $space_remaining) % 2 == 1) {
1076
                            --$effective_space_remaining;
1077
                        }
1078
1079
                        // one block fininshed, store the block data
1080
                        $recordData .= substr($string, 0, $effective_space_remaining);
1081
1082
                        $string = substr($string, $effective_space_remaining); // for next cycle in while loop
1083
                        $recordDatas[] = $recordData;
1084
1085
                        // start new record data block with the repeated option flags
1086
                        $recordData = pack('C', $encoding);
1087
                    }
1088
                }
1089
            }
1090
        }
1091
1092
        // Store the last record data block unless it is empty
1093
        // if there was no need for any continue records, this will be the for SST record data block itself
1094 42
        if (strlen($recordData) > 0) {
1095 42
            $recordDatas[] = $recordData;
1096
        }
1097
1098
        // combine into one chunk with all the blocks SST, CONTINUE,...
1099 42
        $chunk = '';
1100 42
        foreach ($recordDatas as $i => $recordData) {
1101
            // first block should have the SST record header, remaing should have CONTINUE header
1102 42
            $record = ($i == 0) ? 0x00FC : 0x003C;
1103
1104 42
            $header = pack('vv', $record, strlen($recordData));
1105 42
            $data = $header . $recordData;
1106
1107 42
            $chunk .= $this->writeData($data);
1108
        }
1109
1110 42
        return $chunk;
1111
    }
1112
1113
    /**
1114
     * Writes the MSODRAWINGGROUP record if needed. Possibly split using CONTINUE records.
1115
     */
1116 42
    private function writeMsoDrawingGroup()
1117
    {
1118
        // write the Escher stream if necessary
1119 42
        if (isset($this->escher)) {
1120 10
            $writer = new Escher($this->escher);
1121 10
            $data = $writer->close();
1122
1123 10
            $record = 0x00EB;
1124 10
            $length = strlen($data);
1125 10
            $header = pack('vv', $record, $length);
1126
1127 10
            return $this->writeData($header . $data);
1128
        }
1129
1130 32
        return '';
1131
    }
1132
1133
    /**
1134
     * Get Escher object.
1135
     *
1136
     * @return \PhpOffice\PhpSpreadsheet\Shared\Escher
1137
     */
1138
    public function getEscher()
1139
    {
1140
        return $this->escher;
1141
    }
1142
1143
    /**
1144
     * Set Escher object.
1145
     *
1146
     * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue
1147
     */
1148 10
    public function setEscher(\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null)
1149
    {
1150 10
        $this->escher = $pValue;
1151 10
    }
1152
}
1153