Worksheet   F
last analyzed

Complexity

Total Complexity 128

Size/Duplication

Total Lines 1364
Duplicated Lines 1.54 %

Coupling/Cohesion

Components 6
Dependencies 10

Importance

Changes 65
Bugs 6 Features 14
Metric Value
wmc 128
c 65
b 6
f 14
lcom 6
cbo 10
dl 21
loc 1364
rs 0.6314

70 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 1
B close() 0 31 1
A getName() 0 4 1
A getData() 0 4 1
A select() 0 4 1
A unselect() 0 4 1
A activate() 0 4 1
A setFirstSheet() 0 4 1
A protect() 0 5 1
A setColumnWidth() 0 11 1
A setRowHeight() 0 10 1
A setSelection() 0 4 1
A freezePanes() 0 14 3
A thawPanes() 0 21 3
A setPanes() 0 10 2
A storePanes() 0 8 2
B calculateActivePane() 0 14 9
A write() 0 12 4
A looksLikeNumber() 0 4 1
A looksLikeFormula() 0 4 1
A looksLikeUrl() 0 6 3
A writeRow() 0 15 4
A writeCol() 0 11 3
A setOutline() 0 11 1
A setRTL() 0 4 2
A writeNumber() 0 6 1
A writeString() 0 11 2
A writeStringSST() 0 14 1
A addCell() 0 8 1
A writeNote() 0 21 1
A writeBlank() 0 11 2
A writeFormula() 0 15 2
A writeUrl() 0 20 4
A writeUrlWeb() 0 5 1
A writeUrlInternal() 12 12 2
A writeUrlExternal() 9 9 1
A writeUrlLabel() 0 12 3
A storeDimensions() 0 4 1
A storeColsAndRowsInfo() 0 12 3
A storeMergedCells() 0 6 2
A storeMargins() 0 9 1
A storeHeaderAndFooter() 0 7 1
A storeCentering() 0 7 1
A mergeCells() 0 5 1
A storePrintHeaders() 0 5 1
A storeGrid() 0 6 1
A storePageBreaks() 0 14 3
A storeProtection() 0 12 3
A insertBitmap() 0 19 1
C positionImage() 0 58 9
A getColWidth() 0 14 3
A getRowHeight() 0 14 3
A storeZoom() 0 10 2
A setValidation() 0 5 1
A storeDataValidity() 0 12 3
A isSelected() 0 4 1
A isFrozen() 0 4 1
A isRtl() 0 4 1
A getIndex() 0 4 1
A getNewObjectId() 0 6 1
A getDrawings() 0 4 1
A isOutlineOn() 0 4 1
A getOutlineStyle() 0 4 1
A getOutlineBelow() 0 4 1
A getOutlineRight() 0 4 1
A getPrintSetup() 0 4 1
A showGridlines() 0 6 1
A setZoom() 0 11 3
A areGridLinesVisible() 0 4 1
A getZoom() 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 Worksheet 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 Worksheet, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Xls;
4
5
class Worksheet extends BIFFwriter
6
{
7
    const BOF_TYPE = 0x0010;
8
9
    const STATE_VISIBLE = 0x00;
10
    const STATE_HIDDEN = 0x01;
11
    const STATE_VERYHIDDEN = 0x02;
12
13
    const TYPE_SHEET = 0x00;
14
15
    /**
16
     * Name of the Worksheet
17
     * @var string
18
     */
19
    protected $name;
20
21
    /**
22
     * Index for the Worksheet
23
     * @var integer
24
     */
25
    protected $index;
26
27
    /**
28
     * Reference to the (default) Format object for URLs
29
     * @var Format
30
     */
31
    protected $urlFormat;
32
33
    /**
34
     * Reference to the parser used for parsing formulas
35
     * @var FormulaParser
36
     */
37
    protected $formulaParser;
38
39
    /**
40
     * @var Range
41
     */
42
    protected $dimensions;
43
44
    /**
45
     * Array containing format information for columns
46
     * @var array
47
     */
48
    protected $colInfo = array();
49
50
    /**
51
     * Array containing format information for rows
52
     * @var array
53
     */
54
    protected $rowInfo = array();
55
56
    /**
57
     * Range containing the selected area for the worksheet
58
     * @var Range
59
     */
60
    protected $selection = null;
61
62
    /**
63
     * Array containing the panes for the worksheet
64
     * @var array
65
     */
66
    protected $panes = array();
67
68
    /**
69
     * The active pane for the worksheet
70
     * @var integer
71
     */
72
    protected $activePane = 3;
73
74
    /**
75
     * Bit specifying if panes are frozen
76
     * @var integer
77
     */
78
    protected $frozen = 0;
79
80
    /**
81
     * Bit specifying if the worksheet is selected
82
     * @var integer
83
     */
84
    protected $selected = 0;
85
86
    /**
87
     * Whether to display RightToLeft.
88
     * @var integer
89
     */
90
    protected $rtl = 0;
91
92
    /**
93
     * Whether to use outline.
94
     * @var bool
95
     */
96
    protected $outlineOn = true;
97
98
    /**
99
     * Auto outline styles.
100
     * @var bool
101
     */
102
    protected $outlineStyle = false;
103
104
    /**
105
     * Whether to have outline summary below.
106
     * @var bool
107
     */
108
    protected $outlineBelow = true;
109
110
    /**
111
     * Whether to have outline summary at the right.
112
     * @var bool
113
     */
114
    protected $outlineRight = true;
115
116
    /**
117
     * Outline row level.
118
     * @var integer
119
     */
120
    protected $outlineRowLevel = 0;
121
122
    /**
123
     * @var SharedStringsTable
124
     */
125
    protected $sst;
126
127
    /**
128
     * @var Workbook
129
     */
130
    protected $workbook;
131
132
    /**
133
     * Merged cell ranges
134
     * @var array
135
     */
136
    protected $mergedRanges = array();
137
138
    protected $protect = 0;
139
    protected $password = null;
140
141
    protected $validations = array();
142
143
    /**
144
     * Holds last OBJ record id
145
     * @var int
146
     */
147
    protected $lastObjectId = 0;
148
149
    protected $drawings = array();
150
151
    /**
152
     * @var PrintSetup
153
     */
154
    protected $printSetup;
155
156
    protected $screenGridLines = true;
157
158
    /**
159
     * @var float
160
     */
161
    protected $zoom = 100;
162
163
    /**
164
     * Constructor
165
     *
166
     * @param string $name         The name of the new worksheet
167
     * @param integer $index        The index of the new worksheet
168
     * @param Workbook $workbook Parent workbook
169
     * @param SharedStringsTable $sst Workbook's shared strings table
170
     * @param Format $urlFormat  The default format for hyperlinks
171
     * @param FormulaParser $formulaParser The formula parser created for the Workbook
172
     */
173
    public function __construct(
174
        $name,
175
        $index,
176
        $workbook,
177
        $sst,
178
        $urlFormat,
179
        $formulaParser
180
    ) {
181
        $this->name = $name;
182
        $this->index = $index;
183
        $this->workbook = $workbook;
184
        $this->sst = $sst;
185
        $this->urlFormat = $urlFormat;
186
        $this->formulaParser = $formulaParser;
187
188
        $this->dimensions = new Range();
189
        $this->selection = new Range();
190
        $this->printSetup = new PrintSetup();
191
    }
192
193
    /**
194
     * Add data to the beginning of the workbook (note the reverse order)
195
     * and to the end of the workbook.
196
     *
197
     * @see Workbook::save()
198
     *
199
     */
200
    public function close()
201
    {
202
        //save previously written data
203
        $data = $this->getDataAndFlush();
204
205
        $this->appendRecord('Bof', array(static::BOF_TYPE));
206
207
        $this->storeColsAndRowsInfo();
208
        $this->storePrintHeaders();
209
        $this->storeGrid();
210
        $this->appendRecord('Guts', array($this->colInfo, $this->outlineRowLevel));
211
        $this->appendRecord('WsBool', array($this));
212
        $this->storePageBreaks();
213
        $this->storeHeaderAndFooter();
214
        $this->storeCentering();
215
        $this->storeMargins();
216
        $this->appendRecord('PageSetup', array($this));
217
        $this->storeProtection();
218
        $this->storeDimensions();
219
220
        $this->appendRaw($data);
221
222
        $this->appendRecord('Window2', array($this));
223
        $this->storeZoom();
224
        $this->storePanes();
225
        $this->appendRecord('Selection', array($this->selection, $this->activePane));
226
        $this->storeMergedCells();
227
        $this->storeDataValidity();
228
229
        $this->appendRecord('Eof');
230
    }
231
232
    /**
233
     * Retrieve the worksheet name.
234
     * This is usefull when creating worksheets without a name.
235
     *
236
     * @return string The worksheet's name
237
     */
238
    public function getName()
239
    {
240
        return $this->name;
241
    }
242
243
    /**
244
     * Retrieves data from memory in one chunk
245
     *
246
     * @return string The data
247
     */
248
    public function getData()
249
    {
250
        return $this->data;
251
    }
252
253
    /**
254
     * Set this worksheet as a selected worksheet,
255
     * i.e. the worksheet has its tab highlighted.
256
     *
257
     */
258
    public function select()
259
    {
260
        $this->selected = 1;
261
    }
262
263
    /**
264
     *
265
     */
266
    public function unselect()
267
    {
268
        $this->selected = 0;
269
    }
270
271
    /**
272
     * Set this worksheet as the active worksheet,
273
     * i.e. the worksheet that is displayed when the workbook is opened.
274
     * Also set it as selected.
275
     *
276
     */
277
    public function activate()
278
    {
279
        $this->workbook->setActiveSheetIndex($this->index);
280
    }
281
282
    /**
283
     * Set this worksheet as the first visible sheet.
284
     * This is necessary when there are a large number of worksheets and the
285
     * activated worksheet is not visible on the screen.
286
     *
287
     */
288
    public function setFirstSheet()
289
    {
290
        $this->workbook->setFirstSheetIndex($this->index);
291
    }
292
293
    /**
294
     * Set the worksheet protection flag
295
     * to prevent accidental modification and to
296
     * hide formulas if the locked and hidden format properties have been set.
297
     *
298
     * @param string $password The password to use for protecting the sheet.
299
     */
300
    public function protect($password)
301
    {
302
        $this->protect = 1;
303
        $this->password = $password;
304
    }
305
306
    /**
307
     * Set the width of a single column
308
     *
309
     * @param integer $col Column index
310
     * @param integer $width    width to set
311
     * @param mixed $format   The optional XF format to apply to the columns
312
     */
313
    public function setColumnWidth($col, $width, $format = null)
314
    {
315
        $this->colInfo[$col] = array(
316
            'col' => $col,
317
            'col2' => $col,
318
            'width' => $width,
319
            'format' => $format,
320
            'hidden' => $width == 0,
321
            'level' => 0
322
        );
323
    }
324
325
    /**
326
     * This method is used to set the height and format for a row.
327
     * @param integer $row    The row to set
328
     * @param integer $height Height we are giving to the row.
329
     *                        Use null to set XF without setting height
330
     * @param mixed $format XF format we are giving to the row
331
     */
332
    public function setRowHeight($row, $height, $format = null)
333
    {
334
        $this->rowInfo[$row] = array(
335
            'row' => $row,
336
            'height' => $height,
337
            'format' => $format,
338
            'hidden' => $height == 0,
339
            'level' => 0
340
        );
341
    }
342
343
    /**
344
     * Set which cell or cells are selected in a worksheet
345
     *
346
     * @param integer $firstRow    first row in the selected quadrant
347
     * @param integer $firstColumn first column in the selected quadrant
348
     * @param integer $lastRow     last row in the selected quadrant
349
     * @param integer $lastColumn  last column in the selected quadrant
350
     */
351
    public function setSelection($firstRow, $firstColumn, $lastRow = null, $lastColumn = null)
352
    {
353
        $this->selection = new Range($firstRow, $firstColumn, $lastRow, $lastColumn);
354
    }
355
356
    /**
357
     * Set panes and mark them as frozen.
358
     *
359
     * @param array $panes This is the only parameter received and is composed of the following:
360
     *                     0 => Vertical split position,
361
     *                     1 => Horizontal split position
362
     *                     2 => Top row visible
363
     *                     3 => Leftmost column visible
364
     *                     4 => Active pane
365
     */
366
    public function freezePanes($panes)
367
    {
368
        $this->frozen = 1;
369
370
        if (!isset($panes[2])) {
371
            $panes[2] = $panes[0];
372
        }
373
374
        if (!isset($panes[3])) {
375
            $panes[3] = $panes[1];
376
        }
377
378
        $this->setPanes($panes);
379
    }
380
381
    /**
382
     * Set panes and mark them as unfrozen.
383
     *
384
     * @param array $panes This is the only parameter received and is composed of the following:
385
     *                     0 => Vertical split position,
386
     *                     1 => Horizontal split position
387
     *                     2 => Top row visible
388
     *                     3 => Leftmost column visible
389
     *                     4 => Active pane
390
     */
391
    public function thawPanes($panes)
392
    {
393
        $this->frozen = 0;
394
395
        // Convert Excel's row and column units to the internal units.
396
        // The default row height is 12.75
397
        // The default column width is 8.43
398
        // The following slope and intersection values were interpolated.
399
        $panes[0] = 20 * $panes[0] + 255;
400
        $panes[1] = 113.879 * $panes[1] + 390;
401
402
        if (!isset($panes[2])) {
403
            $panes[2] = 0;
404
        }
405
406
        if (!isset($panes[3])) {
407
            $panes[3] = 0;
408
        }
409
410
        $this->setPanes($panes);
411
    }
412
413
    protected function setPanes($panes)
414
    {
415
        if (!isset($panes[4])) {
416
            $panes[4] = $this->calculateActivePane($panes[0], $panes[1]);
417
        }
418
419
        $this->activePane = $panes[4];
420
421
        $this->panes = $panes;
422
    }
423
424
    /**
425
     * Writes the Excel BIFF PANE record.
426
     * The panes can either be frozen or thawed (unfrozen).
427
     * Frozen panes are specified in terms of an integer number of rows and columns.
428
     * Thawed panes are specified in terms of Excel's units for rows and columns.
429
     */
430
    protected function storePanes()
431
    {
432
        if (empty($this->panes)) {
433
            return;
434
        }
435
436
        $this->appendRecord('Pane', $this->panes);
437
    }
438
439
    /**
440
     * Determine which pane should be active. There is also the undocumented
441
     * option to override this should it be necessary: may be removed later.
442
     * @param $x
443
     * @param $y
444
     *
445
     * @return int|null
446
     */
447
    protected function calculateActivePane($x, $y)
448
    {
449
        if ($x != 0 && $y != 0) {
450
            return 0; // Bottom right
451
        } elseif ($x != 0 && $y == 0) {
452
            return 1; // Top right
453
        } elseif ($x == 0 && $y != 0) {
454
            return 2; // Bottom left
455
        } elseif ($x == 0 && $y == 0) {
456
            return 3; // Top left
457
        }
458
459
        return null;
460
    }
461
462
    /**
463
     * Write value to cell
464
     *
465
     * @param integer $row    The row of the cell we are writing to
466
     * @param integer $col    The column of the cell we are writing to
467
     * @param mixed $value What we are writing
468
     * @param mixed $format The optional format to apply to the cell
469
     *
470
     */
471
    public function write($row, $col, $value, $format = null)
472
    {
473
        if ($this->looksLikeNumber($value)) {
474
            $this->writeNumber($row, $col, $value, $format);
475
        } elseif ($this->looksLikeUrl($value)) {
476
            $this->writeUrl($row, $col, $value, '', $format);
477
        } elseif ($this->looksLikeFormula($value)) {
478
            $this->writeFormula($row, $col, $value, $format);
479
        } else {
480
            $this->writeString($row, $col, $value, $format);
481
        }
482
    }
483
484
    /**
485
     * @param $value
486
     *
487
     * @return bool
488
     */
489
    protected function looksLikeNumber($value)
490
    {
491
        return preg_match("/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/", $value) === 1;
492
    }
493
494
    /**
495
     * @param $value
496
     *
497
     * @return bool
498
     */
499
    protected function looksLikeFormula($value)
500
    {
501
        return preg_match("/^=/", $value) === 1;
502
    }
503
504
    /**
505
     * @param $value
506
     *
507
     * @return bool
508
     */
509
    protected function looksLikeUrl($value)
510
    {
511
        return preg_match("/^[fh]tt?p:\/\//", $value) === 1
512
            || preg_match("/^mailto:/", $value) === 1
513
            || preg_match("/^(?:in|ex)ternal:/", $value) === 1;
514
    }
515
516
    /**
517
     * Write an array of values as a row
518
     * @param integer $row    The row we are writing to
519
     * @param integer $col    The first col (leftmost col) we are writing to
520
     * @param array $val    The array of values to write
521
     * @param mixed $format The optional format to apply to the cell
522
     * @throws \Exception
523
     */
524
    public function writeRow($row, $col, $val, $format = null)
525
    {
526
        if (is_array($val)) {
527
            foreach ($val as $v) {
528
                if (is_array($v)) {
529
                    $this->writeCol($row, $col, $v, $format);
530
                } else {
531
                    $this->write($row, $col, $v, $format);
532
                }
533
                $col++;
534
            }
535
        } else {
536
            throw new \Exception('$val needs to be an array');
537
        }
538
    }
539
540
    /**
541
     * Write an array of values as a column
542
     * @param integer $row    The first row (uppermost row) we are writing to
543
     * @param integer $col    The col we are writing to
544
     * @param array $val    The array of values to write
545
     * @param mixed $format The optional format to apply to the cell
546
     * @throws \Exception
547
     */
548
    public function writeCol($row, $col, $val, $format = null)
549
    {
550
        if (is_array($val)) {
551
            foreach ($val as $v) {
552
                $this->write($row, $col, $v, $format);
553
                $row++;
554
            }
555
        } else {
556
            throw new \Exception('$val needs to be an array');
557
        }
558
    }
559
560
    /**
561
     * This method sets the properties for outlining and grouping. The defaults
562
     * correspond to Excel's defaults.
563
     *
564
     * @param bool $visible
565
     * @param bool $symbolsBelow
566
     * @param bool $symbolsRight
567
     * @param bool $autoStyle
568
     */
569
    public function setOutline(
570
        $visible = true,
571
        $symbolsBelow = true,
572
        $symbolsRight = true,
573
        $autoStyle = false
574
    ) {
575
        $this->outlineOn = $visible;
576
        $this->outlineBelow = $symbolsBelow;
577
        $this->outlineRight = $symbolsRight;
578
        $this->outlineStyle = $autoStyle;
579
    }
580
581
    /**
582
     * This method sets the worksheet direction to right-to-left (RTL)
583
     *
584
     * @param bool $rtl
585
     */
586
    public function setRTL($rtl = true)
587
    {
588
        $this->rtl = ($rtl ? 1 : 0);
589
    }
590
591
    /**
592
     * Write a double to the specified row and column (zero indexed).
593
     * An integer can be written as a double. Excel will display an
594
     * integer. $format is optional.
595
     *
596
     * @param integer $row    Zero indexed row
597
     * @param integer $col    Zero indexed column
598
     * @param float $num    The number to write
599
     * @param mixed $format The optional XF format
600
     */
601
    public function writeNumber($row, $col, $num, $format = null)
602
    {
603
        $this->addCell($row, $col);
604
605
        $this->appendRecord('Number', array($row, $col, $num, $format));
606
    }
607
608
    /**
609
     * Write a string to the specified row and column (zero indexed).
610
     * NOTE: there is an Excel 5 defined limit of 255 characters.
611
     * $format is optional.
612
     * @param integer $row    Zero indexed row
613
     * @param integer $col    Zero indexed column
614
     * @param string $str    The string to write
615
     * @param mixed $format The XF format for the cell
616
     */
617
    public function writeString($row, $col, $str, $format = null)
618
    {
619
        if ($str === '') {
620
            $this->writeBlank($row, $col, $format);
621
            return;
622
        }
623
624
        $this->addCell($row, $col);
625
626
        $this->writeStringSST($row, $col, $str, $format);
627
    }
628
629
    /**
630
     * Write a string to the specified row and column (zero indexed).
631
     * @param integer $row    Zero indexed row
632
     * @param integer $col    Zero indexed column
633
     * @param string $str    The string to write
634
     * @param mixed $format The XF format for the cell
635
     */
636
    protected function writeStringSST($row, $col, $str, $format = null)
637
    {
638
        $strIdx = $this->sst->add($str);
639
640
        $this->appendRecord(
641
            'LabelSst',
642
            array(
643
                $row,
644
                $col,
645
                $strIdx,
646
                $format
647
            )
648
        );
649
    }
650
651
    /**
652
     * Check row and col before writing to a cell, and update the sheet's
653
     * dimensions accordingly
654
     * @param integer $row    Zero indexed row
655
     * @param integer $col    Zero indexed column
656
     * @return Cell
657
     */
658
    protected function addCell($row, $col)
659
    {
660
        $cell = new Cell($row, $col);
661
662
        $this->dimensions->expand($cell);
663
664
        return $cell;
665
    }
666
667
    /**
668
     * Writes a note associated with the cell given by the row and column.
669
     * NOTE records don't have a length limit.
670
     * @param integer $row    Zero indexed row
671
     * @param integer $col    Zero indexed column
672
     * @param string $note   The note to write
673
     * @param string $guid comment guid (only for tests)
674
     */
675
    public function writeNote($row, $col, $note, $guid = null)
676
    {
677
        $this->addCell($row, $col);
678
679
        $objId = $this->getNewObjectId();
680
        $this->drawings[] = $objId;
681
682
        $drawing = '0F 00 02 F0 D4 00 00 00 10 00 08 F0 08 00 00 00 02 00 00 00 01 04 00 00 0F 00 03 F0 BC 00 00 00';
683
        $drawing .= ' 0F 00 04 F0 28 00 00 00 01 00 09 F0 10 00 00 00 78 FF 77 A0 00 00 00 00 00 00 00 00 88 FF 77';
684
        $drawing .= ' A0 02 00 0A F0 08 00 00 00 00 04 00 00 05 00 00 00 0F 00 04 F0 84 00 00 00 A2 0C 0A F0 08 00';
685
        $drawing .= ' 00 00 01 04 00 00 00 0A 00 00 B3 00 0B F0 42 00 00 00 80 00 98 2C C4 7D BF 00 00 00 08 00 58';
686
        $drawing .= ' 01 00 00 00 00 80 01 04 00 00 00 81 01 FB F6 D6 00 83 01 FB FE 82 00 8B 01 00 00 4C FF BF 01';
687
        $drawing .= ' 10 00 11 00 C0 01 ED EA A1 00 3F 02 03 00 03 00 BF 03 02 00 0A 00 00 00 10 F0 12 00 00 00 03';
688
        $drawing .= ' 00 01 00 EC 00 00 00 22 00 02 00 53 03 04 00 66 00 00 00 11 F0 00 00 00 00';
689
        $this->appendRecord('MsoDrawing', array($drawing));
690
691
        $this->appendRecord('ObjComment', array($objId, $guid));
692
        $this->appendRecord('MsoDrawing', array('00 00 0D F0 00 00 00 00'));
693
        $this->appendRecord('Txo', array($note));
694
        $this->appendRecord('Note', array($row, $col, $objId));
695
    }
696
697
    /**
698
     * Write a blank cell to the specified row and column (zero indexed).
699
     * A blank cell is used to specify formatting without adding a string
700
     * or a number.
701
     *
702
     * A blank cell without a format serves no purpose. Therefore, we don't write
703
     * a BLANK record unless a format is specified.
704
     *
705
     * @param integer $row    Zero indexed row
706
     * @param integer $col    Zero indexed column
707
     * @param mixed $format The XF format
708
     * @throws \Exception
709
     */
710
    public function writeBlank($row, $col, $format = null)
711
    {
712
        if (!$format) {
713
            // Don't write a blank cell unless it has a format
714
            return;
715
        }
716
717
        $this->addCell($row, $col);
718
719
        $this->appendRecord('Blank', array($row, $col, $format));
720
    }
721
722
    /**
723
     * Write a formula to the specified row and column (zero indexed).
724
     * The textual representation of the formula is passed to the formula parser
725
     * which returns a packed binary string.
726
     *
727
     * @param integer $row     Zero indexed row
728
     * @param integer $col     Zero indexed column
729
     * @param string $formula The formula text string
730
     * @param mixed $format  The optional XF format
731
     * @throws \Exception
732
     */
733
    public function writeFormula($row, $col, $formula, $format = null)
734
    {
735
        $this->addCell($row, $col);
736
737
        // Strip the '=' or '@' sign at the beginning of the formula string
738
        if (in_array($formula[0], array('=', '@'), true)) {
739
            $formula = substr($formula, 1);
740
        } else {
741
            throw new \Exception('Invalid formula: should start with = or @');
742
        }
743
744
        $formula = $this->formulaParser->getReversePolish($formula);
745
746
        $this->appendRecord('Formula', array($row, $col, $formula, $format));
747
    }
748
749
    /**
750
     * Write a hyperlink.
751
     * This is comprised of two elements: the visible label and
752
     * the invisible link. The visible label is the same as the link unless an
753
     * alternative string is specified. The label is written using the
754
     * writeString() method. Therefore the 255 characters string limit applies.
755
     * $string and $format are optional.
756
     *
757
     * The hyperlink can be to a http, ftp, mail, internal sheet (not yet), or external
758
     * directory url.
759
     *
760
     * @param integer $row    Row
761
     * @param integer $col    Column
762
     * @param string $url    URL string
763
     * @param string $label Alternative label
764
     * @param mixed $format The cell format
765
     */
766
    public function writeUrl($row, $col, $url, $label = '', $format = null)
767
    {
768
        $this->addCell($row, $col);
769
770
        $range = new Range($row, $col);
771
772
        if (preg_match('[^internal:]', $url)
773
            || strpos($url, '#') === 0
774
        ) {
775
            $this->writeUrlInternal($range, $url, $label, $format);
776
            return;
777
        }
778
779
        if (preg_match('[^external:]', $url)) {
780
            $this->writeUrlExternal($range, $url, $label, $format);
781
            return;
782
        }
783
784
        $this->writeUrlWeb($range, $url, $label, $format);
785
    }
786
787
    /**
788
     * Used to write http, ftp and mailto hyperlinks.
789
     * @param Range $range   Cell range
790
     * @param string $url    URL string
791
     * @param string $str    Alternative label
792
     * @param mixed $format The cell format
793
     */
794
    protected function writeUrlWeb(Range $range, $url, $str, $format = null)
795
    {
796
        $this->writeUrlLabel($range->getStartCell(), $url, $str, $format);
797
        $this->appendRecord('Hyperlink', array($range, $url));
798
    }
799
800
    /**
801
     * Used to write internal reference hyperlinks such as "Sheet1!A1".
802
     *
803
     * @param Range $range Cell range
804
     * @param string $url    URL string
805
     * @param string $label    Alternative label
806
     * @param mixed $format The cell format
807
     */
808 View Code Duplication
    protected function writeUrlInternal(Range $range, $url, $label, $format = null)
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...
809
    {
810
        // Strip URL type
811
        $url = preg_replace('/^internal:/', '', $url);
812
813
        if (strpos($url, '#') === 0) {
814
            $url = substr($url, 1);
815
        }
816
817
        $this->writeUrlLabel($range->getStartCell(), $url, $label, $format);
818
        $this->appendRecord('HyperlinkInternal', array($range, $url));
819
    }
820
821
    /**
822
     * Write links to external directory names such as 'c:\foo.xls',
823
     * c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
824
     *
825
     * @param Range $range Cell range
826
     * @param string $url    URL string
827
     * @param string $label    Alternative label
828
     * @param mixed $format The cell format
829
     */
830 View Code Duplication
    protected function writeUrlExternal(Range $range, $url, $label, $format = null)
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...
831
    {
832
        // Strip URL type and change Unix dir separator to Dos style (if needed)
833
        $url = preg_replace('/^external:/', '', $url);
834
        $url = preg_replace('/\//', "\\", $url);
835
836
        $this->writeUrlLabel($range->getStartCell(), $url, $label, $format);
837
        $this->appendRecord('HyperlinkExternal', array($range, $url));
838
    }
839
840
    /**
841
     * @param Cell $cell
842
     * @param string $url
843
     * @param string $str
844
     * @param null $format
845
     */
846
    protected function writeUrlLabel(Cell $cell, $url, $str, $format = null)
847
    {
848
        if (!$format) {
849
            $format = $this->urlFormat;
850
        }
851
852
        if ($str == '') {
853
            $str = $url;
854
        }
855
856
        $this->writeString($cell->getRow(), $cell->getCol(), $str, $format);
857
    }
858
859
    /**
860
     * Writes Excel DIMENSIONS to define the area in which there is data.
861
     * @throw \Exception
862
     */
863
    protected function storeDimensions()
864
    {
865
        $this->appendRecord('Dimensions', array($this->dimensions));
866
    }
867
868
    /**
869
     * Append the COLINFO and ROW records if they exist
870
     */
871
    protected function storeColsAndRowsInfo()
872
    {
873
        $this->appendRecord('Defcolwidth');
874
875
        foreach ($this->colInfo as $item) {
876
            $this->appendRecord('Colinfo', array($item));
877
        }
878
879
        foreach ($this->rowInfo as $item) {
880
            $this->appendRecord('Row', array($item));
881
        }
882
    }
883
884
    /**
885
     * Store the MERGECELLS record for all ranges of merged cells
886
     */
887
    protected function storeMergedCells()
888
    {
889
        if (count($this->mergedRanges) > 0) {
890
            $this->appendRecord('MergeCells', array($this->mergedRanges));
891
        }
892
    }
893
894
    /**
895
     * Store the margins records
896
     */
897
    protected function storeMargins()
898
    {
899
        $margin = $this->getPrintSetup()->getMargin();
900
901
        $this->appendRecord('LeftMargin', array($margin->getLeft()));
902
        $this->appendRecord('RightMargin', array($margin->getRight()));
903
        $this->appendRecord('TopMargin', array($margin->getTop()));
904
        $this->appendRecord('BottomMargin', array($margin->getBottom()));
905
    }
906
907
    protected function storeHeaderAndFooter()
908
    {
909
        $printSetup = $this->getPrintSetup();
910
911
        $this->appendRecord('Header', array($printSetup->getHeader()));
912
        $this->appendRecord('Footer', array($printSetup->getFooter()));
913
    }
914
915
    /**
916
     *
917
     */
918
    protected function storeCentering()
919
    {
920
        $printSetup = $this->getPrintSetup();
921
922
        $this->appendRecord('Hcenter', array((int)$printSetup->isHcenteringOn()));
923
        $this->appendRecord('Vcenter', array((int)$printSetup->isVcenteringOn()));
924
    }
925
926
    /**
927
     * Merges the area given by its arguments.
928
     * @param integer $firstRow First row of the area to merge
929
     * @param integer $firstCol First column of the area to merge
930
     * @param integer $lastRow  Last row of the area to merge
931
     * @param integer $lastCol  Last column of the area to merge
932
     */
933
    public function mergeCells($firstRow, $firstCol, $lastRow, $lastCol)
934
    {
935
        $range = new Range($firstRow, $firstCol, $lastRow, $lastCol);
936
        $this->mergedRanges[] = $range;
937
    }
938
939
    /**
940
     * Write the PRINTHEADERS BIFF record.
941
     */
942
    protected function storePrintHeaders()
943
    {
944
        $printHeaders = $this->getPrintSetup()->shouldPrintRowColHeaders();
945
        $this->appendRecord('PrintHeaders', array($printHeaders));
946
    }
947
948
    /**
949
     * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the
950
     * GRIDSET record.
951
     */
952
    protected function storeGrid()
953
    {
954
        $linesVisible = $this->getPrintSetup()->shouldPrintGridLines();
955
        $this->appendRecord('PrintGridLines', array($linesVisible));
956
        $this->appendRecord('Gridset', array(!$linesVisible));
957
    }
958
959
    /**
960
     *
961
     */
962
    protected function storePageBreaks()
963
    {
964
        $printSetup = $this->getPrintSetup();
965
966
        $hbreaks = $printSetup->getHbreaks();
967
        if (!empty($hbreaks)) {
968
            $this->appendRecord('HorizontalPagebreaks', array($hbreaks));
969
        }
970
971
        $vbreaks = $printSetup->getVbreaks();
972
        if (!empty($vbreaks)) {
973
            $this->appendRecord('VerticalPagebreaks', array($vbreaks));
974
        }
975
    }
976
977
    /**
978
     * Write sheet protection
979
     */
980
    protected function storeProtection()
981
    {
982
        if (!$this->protect) {
983
            return;
984
        }
985
986
        $this->appendRecord('Protect', array($this->protect));
987
988
        if (isset($this->password)) {
989
            $this->appendRecord('Password', array($this->password));
990
        }
991
    }
992
993
    /**
994
     * Insert a 24bit bitmap image in a worksheet.
995
     *
996
     * @param integer $row     The row we are going to insert the bitmap into
997
     * @param integer $col     The column we are going to insert the bitmap into
998
     * @param string $path  The bitmap filename
999
     * @param integer $x       The horizontal position (offset) of the image inside the cell.
1000
     * @param integer $y       The vertical position (offset) of the image inside the cell.
1001
     * @param integer $scaleX The horizontal scale
1002
     * @param integer $scaleY The vertical scale
1003
     */
1004
    public function insertBitmap($row, $col, $path, $x = 0, $y = 0, $scaleX = 1, $scaleY = 1)
1005
    {
1006
        $bmp = new Bitmap($path);
1007
1008
        $width = $bmp->getWidth();
1009
        $height = $bmp->getHeight();
1010
1011
        // BITMAPCOREINFO
1012
        $data = $this->getRecord('BitmapCoreHeader', array($width, $height));
1013
        $data .= $bmp->getDataWithoutHeader();
1014
1015
        // Scale the frame of the image.
1016
        $width *= $scaleX;
1017
        $height *= $scaleY;
1018
1019
        $this->positionImage($col, $row, $x, $y, $width, $height);
1020
1021
        $this->appendRecord('Imdata', array($data));
1022
    }
1023
1024
    /**
1025
     * Calculate the vertices that define the position of the image as required by
1026
     * the OBJ record.
1027
     *
1028
     *         +------------+------------+
1029
     *         |     A      |      B     |
1030
     *   +-----+------------+------------+
1031
     *   |     |(x1,y1)     |            |
1032
     *   |  1  |(A1)._______|______      |
1033
     *   |     |    |              |     |
1034
     *   |     |    |              |     |
1035
     *   +-----+----|    BITMAP    |-----+
1036
     *   |     |    |              |     |
1037
     *   |  2  |    |______________.     |
1038
     *   |     |            |        (B2)|
1039
     *   |     |            |     (x2,y2)|
1040
     *   +---- +------------+------------+
1041
     *
1042
     * Example of a bitmap that covers some of the area from cell A1 to cell B2.
1043
     *
1044
     * Based on the width and height of the bitmap we need to calculate 8 vars:
1045
     *     $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
1046
     * The width and height of the cells are also variable and have to be taken into
1047
     * account.
1048
     * The values of $col_start and $row_start are passed in from the calling
1049
     * function. The values of $col_end and $row_end are calculated by subtracting
1050
     * the width and height of the bitmap from the width and height of the
1051
     * underlying cells.
1052
     * The vertices are expressed as a percentage of the underlying cell width as
1053
     * follows (rhs values are in pixels):
1054
     *
1055
     *       x1 = X / W *1024
1056
     *       y1 = Y / H *256
1057
     *       x2 = (X-1) / W *1024
1058
     *       y2 = (Y-1) / H *256
1059
     *
1060
     *       Where:  X is distance from the left side of the underlying cell
1061
     *               Y is distance from the top of the underlying cell
1062
     *               W is the width of the cell
1063
     *               H is the height of the cell
1064
     *
1065
     *
1066
     * @note  the SDK incorrectly states that the height should be expressed as a
1067
     *        percentage of 1024.
1068
     *
1069
     * @param integer $colStart Col containing upper left corner of object
1070
     * @param integer $rowStart Row containing top left corner of object
1071
     * @param integer $x1        Distance to left side of object
1072
     * @param integer $y1        Distance to top of object
1073
     * @param integer $width     Width of image frame
1074
     * @param integer $height    Height of image frame
1075
     * @throws \Exception
1076
     */
1077
    protected function positionImage($colStart, $rowStart, $x1, $y1, $width, $height)
1078
    {
1079
        // Initialise end cell to the same as the start cell
1080
        $colEnd = $colStart; // Col containing lower right corner of object
1081
        $rowEnd = $rowStart; // Row containing bottom right corner of object
1082
1083
        // Zero the specified offset if greater than the cell dimensions
1084
        $colStartSize = $this->getColWidth($colStart);
1085
        if ($x1 >= $colStartSize) {
1086
            $x1 = 0;
1087
        }
1088
1089
        $rowStartSize = $this->getRowHeight($rowStart);
1090
        if ($y1 >= $rowStartSize) {
1091
            $y1 = 0;
1092
        }
1093
1094
        $width = $width + $x1 - 1;
1095
        $height = $height + $y1 - 1;
1096
1097
        // Subtract the underlying cell widths to find the end cell of the image
1098
        while ($width >= $this->getColWidth($colEnd)) {
1099
            $width -= $this->getColWidth($colEnd);
1100
            $colEnd++;
1101
        }
1102
1103
        // Subtract the underlying cell heights to find the end cell of the image
1104
        while ($height >= $this->getRowHeight($rowEnd)) {
1105
            $height -= $this->getRowHeight($rowEnd);
1106
            $rowEnd++;
1107
        }
1108
1109
        $colEndSize = $this->getColWidth($colEnd);
1110
        $rowEndSize = $this->getRowHeight($rowEnd);
1111
1112
        if ($colStartSize == 0
1113
            || $colEndSize == 0
1114
            || $rowStartSize == 0
1115
            || $rowEndSize == 0
1116
        ) {
1117
            throw new \Exception('Bitmap isn\'t allowed to start or finish in a hidden cell');
1118
        }
1119
1120
        // Convert the pixel values to the percentage value expected by Excel
1121
        $x1 = $x1 / $colStartSize * 1024;
1122
        $y1 = $y1 / $rowStartSize * 256;
1123
        $x2 = $width / $colEndSize * 1024; // Distance to right side of object
1124
        $y2 = $height / $rowEndSize * 256; // Distance to bottom of object
1125
1126
        $this->appendRecord(
1127
            'ObjPicture',
1128
            array(
1129
                $this->getNewObjectId(),
1130
                new Range($rowStart, $colStart, $rowEnd, $colEnd, false),
1131
                new Margin($x1, $x2, $y1, $y2)
1132
            )
1133
        );
1134
    }
1135
1136
    /**
1137
     * Convert the width of a cell from user's units to pixels. By interpolation
1138
     * the relationship is: y = 7x +5. If the width hasn't been set by the user we
1139
     * use the default value. If the col is hidden we use a value of zero.
1140
     *
1141
     *
1142
     * @param integer $col The column
1143
     * @return integer The width in pixels
1144
     */
1145
    protected function getColWidth($col)
1146
    {
1147
        // Look up the cell value to see if it has been changed
1148
        if (isset($this->colInfo[$col])) {
1149
            $width = $this->colInfo[$col]['width'];
1150
            if ($width == 0) {
1151
                return 0;
1152
            }
1153
1154
            return floor(7 * $width + 5);
1155
        }
1156
1157
        return 64;
1158
    }
1159
1160
    /**
1161
     * Convert the height of a cell from user's units to pixels. By interpolation
1162
     * the relationship is: y = 4/3x. If the height hasn't been set by the user we
1163
     * use the default value. If the row is hidden we use a value of zero. (Not
1164
     * possible to hide row yet).
1165
     *
1166
     *
1167
     * @param integer $row The row
1168
     * @return integer The width in pixels
1169
     */
1170
    protected function getRowHeight($row)
1171
    {
1172
        // Look up the cell value to see if it has been changed
1173
        if (isset($this->rowInfo[$row])) {
1174
            $height = $this->rowInfo[$row]['height'];
1175
            if ($height == 0) {
1176
                return 0;
1177
            }
1178
1179
            return floor(4 / 3 * $height);
1180
        }
1181
1182
        return 17;
1183
    }
1184
1185
    /**
1186
     * Store the window zoom factor. This should be a reduced fraction but for
1187
     * simplicity we will store all fractions with a numerator of 100.
1188
     */
1189
    protected function storeZoom()
1190
    {
1191
        // If scale is 100 we don't need to write a record
1192
        $zoom = $this->getZoom();
1193
        if ($zoom == 100) {
1194
            return;
1195
        }
1196
1197
        $this->appendRecord('Zoom', array($zoom));
1198
    }
1199
1200
    /**
1201
     * @param $row1
1202
     * @param $col1
1203
     * @param $row2
1204
     * @param $col2
1205
     * @param Validator $validator
1206
     */
1207
    public function setValidation($row1, $col1, $row2, $col2, $validator)
1208
    {
1209
        $range = new Range($row1, $col1, $row2, $col2);
1210
        $this->validations[] = $validator->getData($range);
1211
    }
1212
1213
    /**
1214
     * Store the DVAL and DV records.
1215
     */
1216
    protected function storeDataValidity()
1217
    {
1218
        if (count($this->validations) == 0) {
1219
            return;
1220
        }
1221
1222
        $this->appendRecord('DataValidations', array($this->validations));
1223
1224
        foreach ($this->validations as $dv) {
1225
            $this->appendRecord('DataValidation', array($dv));
1226
        }
1227
    }
1228
1229
    /**
1230
     * @return bool
1231
     */
1232
    public function isSelected()
1233
    {
1234
        return (bool)$this->selected;
1235
    }
1236
1237
    /**
1238
     * @return bool
1239
     */
1240
    public function isFrozen()
1241
    {
1242
        return (bool)$this->frozen;
1243
    }
1244
1245
    /**
1246
     * @return bool
1247
     */
1248
    public function isRtl()
1249
    {
1250
        return (bool)$this->rtl;
1251
    }
1252
1253
    /**
1254
     * @return int
1255
     */
1256
    public function getIndex()
1257
    {
1258
        return $this->index;
1259
    }
1260
1261
    /**
1262
     * @return integer
1263
     */
1264
    protected function getNewObjectId()
1265
    {
1266
        $this->lastObjectId++;
1267
1268
        return $this->lastObjectId;
1269
    }
1270
1271
    /**
1272
     * @return array
1273
     */
1274
    public function getDrawings()
1275
    {
1276
        return $this->drawings;
1277
    }
1278
1279
    /**
1280
     * @return bool
1281
     */
1282
    public function isOutlineOn()
1283
    {
1284
        return $this->outlineOn;
1285
    }
1286
1287
    /**
1288
     * @return boolean
1289
     */
1290
    public function getOutlineStyle()
1291
    {
1292
        return $this->outlineStyle;
1293
    }
1294
1295
    /**
1296
     * @return boolean
1297
     */
1298
    public function getOutlineBelow()
1299
    {
1300
        return $this->outlineBelow;
1301
    }
1302
1303
    /**
1304
     * @return boolean
1305
     */
1306
    public function getOutlineRight()
1307
    {
1308
        return $this->outlineRight;
1309
    }
1310
1311
    /**
1312
     * @return PrintSetup
1313
     */
1314
    public function getPrintSetup()
1315
    {
1316
        return $this->printSetup;
1317
    }
1318
1319
    /**
1320
     * Set the option to hide gridlines on the worksheet (as seen on the screen).
1321
     *
1322
     * @param bool $visible
1323
     *
1324
     * @return Worksheet
1325
     */
1326
    public function showGridlines($visible = true)
1327
    {
1328
        $this->screenGridLines = $visible;
1329
1330
        return $this;
1331
    }
1332
1333
    /**
1334
     * Set the worksheet zoom factor.
1335
     *
1336
     * @param integer $percents The zoom factor
1337
     *
1338
     * @throws \Exception
1339
     * @return Worksheet
1340
     */
1341
    public function setZoom($percents = 100)
1342
    {
1343
        // Confine the scale to Excel's range
1344
        if ($percents < 10 || $percents > 400) {
1345
            throw new \Exception("Zoom factor $percents outside range: 10 <= zoom <= 400");
1346
        }
1347
1348
        $this->zoom = floor($percents);
1349
1350
        return $this;
1351
    }
1352
1353
    /**
1354
     * @return bool
1355
     */
1356
    public function areGridLinesVisible()
1357
    {
1358
        return (bool)$this->screenGridLines;
1359
    }
1360
1361
    /**
1362
     * @return float
1363
     */
1364
    public function getZoom()
1365
    {
1366
        return $this->zoom;
1367
    }
1368
}
1369