Completed
Push — develop ( 782b4e...557e80 )
by Adrien
43:38
created

Cell::getValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Cell;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Collection\Cells;
7
use PhpOffice\PhpSpreadsheet\Exception;
8
use PhpOffice\PhpSpreadsheet\RichText\RichText;
9
use PhpOffice\PhpSpreadsheet\Style;
10
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
11
12
class Cell
13
{
14
    /**
15
     * Default range variable constant.
16
     *
17
     * @var string
18
     */
19
    const DEFAULT_RANGE = 'A1:A1';
20
21
    /**
22
     * Value binder to use.
23
     *
24
     * @var IValueBinder
25
     */
26
    private static $valueBinder;
27
28
    /**
29
     * Value of the cell.
30
     *
31
     * @var mixed
32
     */
33
    private $value;
34
35
    /**
36
     *    Calculated value of the cell (used for caching)
37
     *    This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
38
     *        create the original spreadsheet file.
39
     *    Note that this value is not guaranteed to reflect the actual calculated value because it is
40
     *        possible that auto-calculation was disabled in the original spreadsheet, and underlying data
41
     *        values used by the formula have changed since it was last calculated.
42
     *
43
     * @var mixed
44
     */
45
    private $calculatedValue;
46
47
    /**
48
     * Type of the cell data.
49
     *
50
     * @var string
51
     */
52
    private $dataType;
53
54
    /**
55
     * Collection of cells.
56
     *
57
     * @var Cells
58
     */
59
    private $parent;
60
61
    /**
62
     * Index to cellXf.
63
     *
64
     * @var int
65
     */
66
    private $xfIndex = 0;
67
68
    /**
69
     * Attributes of the formula.
70
     */
71
    private $formulaAttributes;
72
73
    /**
74
     * Update the cell into the cell collection.
75
     *
76
     * @return self
77
     */
78 120
    public function updateInCollection()
79
    {
80 120
        $this->parent->update($this);
81
82 120
        return $this;
83
    }
84
85 110
    public function detach()
86
    {
87 110
        $this->parent = null;
88 110
    }
89
90 102
    public function attach(Cells $parent)
91
    {
92 102
        $this->parent = $parent;
93 102
    }
94
95
    /**
96
     * Create a new Cell.
97
     *
98
     * @param mixed $pValue
99
     * @param string $pDataType
100
     * @param Worksheet $pSheet
101
     *
102
     * @throws Exception
103
     */
104 122
    public function __construct($pValue, $pDataType, Worksheet $pSheet)
105
    {
106
        // Initialise cell value
107 122
        $this->value = $pValue;
108
109
        // Set worksheet cache
110 122
        $this->parent = $pSheet->getCellCollection();
111
112
        // Set datatype?
113 122
        if ($pDataType !== null) {
114 122
            if ($pDataType == DataType::TYPE_STRING2) {
115
                $pDataType = DataType::TYPE_STRING;
116
            }
117 122
            $this->dataType = $pDataType;
118
        } elseif (!self::getValueBinder()->bindValue($this, $pValue)) {
119
            throw new Exception('Value could not be bound to cell.');
120
        }
121 122
    }
122
123
    /**
124
     * Get cell coordinate column.
125
     *
126
     * @return string
127
     */
128 48
    public function getColumn()
129
    {
130 48
        return $this->parent->getCurrentColumn();
131
    }
132
133
    /**
134
     * Get cell coordinate row.
135
     *
136
     * @return int
137
     */
138 46
    public function getRow()
139
    {
140 46
        return $this->parent->getCurrentRow();
141
    }
142
143
    /**
144
     * Get cell coordinate.
145
     *
146
     * @return string
147
     */
148 122
    public function getCoordinate()
149
    {
150 122
        return $this->parent->getCurrentCoordinate();
151
    }
152
153
    /**
154
     * Get cell value.
155
     *
156
     * @return mixed
157
     */
158 107
    public function getValue()
159
    {
160 107
        return $this->value;
161
    }
162
163
    /**
164
     * Get cell value with formatting.
165
     *
166
     * @return string
167
     */
168 5
    public function getFormattedValue()
169
    {
170 5
        return (string) Style\NumberFormat::toFormattedString(
171 5
            $this->getCalculatedValue(),
172 5
            $this->getStyle()
173 5
                ->getNumberFormat()->getFormatCode()
174
        );
175
    }
176
177
    /**
178
     * Set cell value.
179
     *
180
     *    Sets the value for a cell, automatically determining the datatype using the value binder
181
     *
182
     * @param mixed $pValue Value
183
     *
184
     * @throws Exception
185
     *
186
     * @return Cell
187
     */
188 78
    public function setValue($pValue)
189
    {
190 78
        if (!self::getValueBinder()->bindValue($this, $pValue)) {
191
            throw new Exception('Value could not be bound to cell.');
192
        }
193
194 78
        return $this;
195
    }
196
197
    /**
198
     * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder).
199
     *
200
     * @param mixed $pValue Value
201
     * @param string $pDataType Explicit data type, see DataType::TYPE_*
202
     *
203
     * @throws Exception
204
     *
205
     * @return Cell
206
     */
207 116
    public function setValueExplicit($pValue, $pDataType)
208
    {
209
        // set the value according to data type
210
        switch ($pDataType) {
211 116
            case DataType::TYPE_NULL:
212 29
                $this->value = $pValue;
213
214 29
                break;
215 116
            case DataType::TYPE_STRING2:
216
                $pDataType = DataType::TYPE_STRING;
217
                // no break
218 116
            case DataType::TYPE_STRING:
219
                // Synonym for string
220 99
            case DataType::TYPE_INLINE:
221
                // Rich text
222 100
                $this->value = DataType::checkString($pValue);
223
224 100
                break;
225 98
            case DataType::TYPE_NUMERIC:
226 92
                $this->value = (float) $pValue;
227
228 92
                break;
229 59
            case DataType::TYPE_FORMULA:
230 57
                $this->value = (string) $pValue;
231
232 57
                break;
233 25
            case DataType::TYPE_BOOL:
234 25
                $this->value = (bool) $pValue;
235
236 25
                break;
237
            case DataType::TYPE_ERROR:
238
                $this->value = DataType::checkErrorCode($pValue);
239
240
                break;
241
            default:
242
                throw new Exception('Invalid datatype: ' . $pDataType);
243
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
244
        }
245
246
        // set the datatype
247 116
        $this->dataType = $pDataType;
248
249 116
        return $this->updateInCollection();
250
    }
251
252
    /**
253
     * Get calculated cell value.
254
     *
255
     * @param bool $resetLog Whether the calculation engine logger should be reset or not
256
     *
257
     * @throws Exception
258
     *
259
     * @return mixed
260
     */
261 70
    public function getCalculatedValue($resetLog = true)
262
    {
263 70
        if ($this->dataType == DataType::TYPE_FORMULA) {
264
            try {
265 48
                $result = Calculation::getInstance(
266 48
                    $this->getWorksheet()->getParent()
267 48
                )->calculateCellValue($this, $resetLog);
268
                //    We don't yet handle array returns
269 48
                if (is_array($result)) {
270 48
                    while (is_array($result)) {
271 10
                        $result = array_pop($result);
272
                    }
273
                }
274
            } catch (Exception $ex) {
275
                if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
276
                    return $this->calculatedValue; // Fallback for calculations referencing external files.
277
                }
278
279
                throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception(
280
                    $this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage()
281
                );
282
            }
283
284 48
            if ($result === '#Not Yet Implemented') {
285
                return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
286
            }
287
288 48
            return $result;
289 70
        } elseif ($this->value instanceof RichText) {
290 2
            return $this->value->getPlainText();
291
        }
292
293 70
        return $this->value;
294
    }
295
296
    /**
297
     * Set old calculated value (cached).
298
     *
299
     * @param mixed $pValue Value
300
     *
301
     * @return Cell
302
     */
303 22
    public function setCalculatedValue($pValue)
304
    {
305 22
        if ($pValue !== null) {
306 22
            $this->calculatedValue = (is_numeric($pValue)) ? (float) $pValue : $pValue;
307
        }
308
309 22
        return $this->updateInCollection();
310
    }
311
312
    /**
313
     *    Get old calculated value (cached)
314
     *    This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
315
     *        create the original spreadsheet file.
316
     *    Note that this value is not guaranteed to refelect the actual calculated value because it is
317
     *        possible that auto-calculation was disabled in the original spreadsheet, and underlying data
318
     *        values used by the formula have changed since it was last calculated.
319
     *
320
     * @return mixed
321
     */
322
    public function getOldCalculatedValue()
323
    {
324
        return $this->calculatedValue;
325
    }
326
327
    /**
328
     * Get cell data type.
329
     *
330
     * @return string
331
     */
332 70
    public function getDataType()
333
    {
334 70
        return $this->dataType;
335
    }
336
337
    /**
338
     * Set cell data type.
339
     *
340
     * @param string $pDataType see DataType::TYPE_*
341
     *
342
     * @return Cell
343
     */
344
    public function setDataType($pDataType)
345
    {
346
        if ($pDataType == DataType::TYPE_STRING2) {
347
            $pDataType = DataType::TYPE_STRING;
348
        }
349
        $this->dataType = $pDataType;
350
351
        return $this->updateInCollection();
352
    }
353
354
    /**
355
     * Identify if the cell contains a formula.
356
     *
357
     * @return bool
358
     */
359
    public function isFormula()
360
    {
361
        return $this->dataType == DataType::TYPE_FORMULA;
362
    }
363
364
    /**
365
     *    Does this cell contain Data validation rules?
366
     *
367
     * @throws Exception
368
     *
369
     * @return bool
370
     */
371
    public function hasDataValidation()
372
    {
373
        if (!isset($this->parent)) {
374
            throw new Exception('Cannot check for data validation when cell is not bound to a worksheet');
375
        }
376
377
        return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
378
    }
379
380
    /**
381
     * Get Data validation rules.
382
     *
383
     * @throws Exception
384
     *
385
     * @return DataValidation
386
     */
387 2
    public function getDataValidation()
388
    {
389 2
        if (!isset($this->parent)) {
390
            throw new Exception('Cannot get data validation for cell that is not bound to a worksheet');
391
        }
392
393 2
        return $this->getWorksheet()->getDataValidation($this->getCoordinate());
394
    }
395
396
    /**
397
     * Set Data validation rules.
398
     *
399
     * @param DataValidation $pDataValidation
400
     *
401
     * @throws Exception
402
     *
403
     * @return Cell
404
     */
405 View Code Duplication
    public function setDataValidation(DataValidation $pDataValidation = 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...
406
    {
407
        if (!isset($this->parent)) {
408
            throw new Exception('Cannot set data validation for cell that is not bound to a worksheet');
409
        }
410
411
        $this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation);
412
413
        return $this->updateInCollection();
414
    }
415
416
    /**
417
     *    Does this cell contain a Hyperlink?
418
     *
419
     * @throws Exception
420
     *
421
     * @return bool
422
     */
423
    public function hasHyperlink()
424
    {
425
        if (!isset($this->parent)) {
426
            throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet');
427
        }
428
429
        return $this->getWorksheet()->hyperlinkExists($this->getCoordinate());
430
    }
431
432
    /**
433
     * Get Hyperlink.
434
     *
435
     * @throws Exception
436
     *
437
     * @return Hyperlink
438
     */
439 21
    public function getHyperlink()
440
    {
441 21
        if (!isset($this->parent)) {
442
            throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
443
        }
444
445 21
        return $this->getWorksheet()->getHyperlink($this->getCoordinate());
446
    }
447
448
    /**
449
     * Set Hyperlink.
450
     *
451
     * @param Hyperlink $pHyperlink
452
     *
453
     * @throws Exception
454
     *
455
     * @return Cell
456
     */
457 View Code Duplication
    public function setHyperlink(Hyperlink $pHyperlink = 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...
458
    {
459
        if (!isset($this->parent)) {
460
            throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
461
        }
462
463
        $this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink);
464
465
        return $this->updateInCollection();
466
    }
467
468
    /**
469
     * Get cell collection.
470
     *
471
     * @return Cells
472
     */
473 63
    public function getParent()
474
    {
475 63
        return $this->parent;
476
    }
477
478
    /**
479
     * Get parent worksheet.
480
     *
481
     * @return Worksheet
482
     */
483 77
    public function getWorksheet()
484
    {
485 77
        return $this->parent->getParent();
486
    }
487
488
    /**
489
     * Is this cell in a merge range.
490
     *
491
     * @return bool
492
     */
493
    public function isInMergeRange()
494
    {
495
        return (bool) $this->getMergeRange();
496
    }
497
498
    /**
499
     * Is this cell the master (top left cell) in a merge range (that holds the actual data value).
500
     *
501
     * @return bool
502
     */
503
    public function isMergeRangeValueCell()
504
    {
505
        if ($mergeRange = $this->getMergeRange()) {
506
            $mergeRange = self::splitRange($mergeRange);
0 ignored issues
show
Documentation introduced by
$mergeRange is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
507
            list($startCell) = $mergeRange[0];
508
            if ($this->getCoordinate() === $startCell) {
509
                return true;
510
            }
511
        }
512
513
        return false;
514
    }
515
516
    /**
517
     * If this cell is in a merge range, then return the range.
518
     *
519
     * @return string
520
     */
521
    public function getMergeRange()
522
    {
523
        foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) {
524
            if ($this->isInRange($mergeRange)) {
0 ignored issues
show
Documentation introduced by
$mergeRange is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
525
                return $mergeRange;
526
            }
527
        }
528
529
        return false;
530
    }
531
532
    /**
533
     * Get cell style.
534
     *
535
     * @return Style
536
     */
537 6
    public function getStyle()
538
    {
539 6
        return $this->getWorksheet()->getStyle($this->getCoordinate());
540
    }
541
542
    /**
543
     * Re-bind parent.
544
     *
545
     * @param Worksheet $parent
546
     *
547
     * @return Cell
548
     */
549
    public function rebindParent(Worksheet $parent)
550
    {
551
        $this->parent = $parent->getCellCollection();
552
553
        return $this->updateInCollection();
554
    }
555
556
    /**
557
     *    Is cell in a specific range?
558
     *
559
     * @param string $pRange Cell range (e.g. A1:A1)
560
     *
561
     * @return bool
562
     */
563
    public function isInRange($pRange)
564
    {
565
        list($rangeStart, $rangeEnd) = self::rangeBoundaries($pRange);
566
567
        // Translate properties
568
        $myColumn = self::columnIndexFromString($this->getColumn());
569
        $myRow = $this->getRow();
570
571
        // Verify if cell is in range
572
        return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) &&
573
                ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow);
574
    }
575
576
    /**
577
     * Coordinate from string.
578
     *
579
     * @param string $pCoordinateString eg: 'A1'
580
     *
581
     * @throws Exception
582
     *
583
     * @return string[] Array containing column and row (indexes 0 and 1)
584
     */
585 171
    public static function coordinateFromString($pCoordinateString)
586
    {
587 171
        if (preg_match("/^([$]?[A-Z]{1,3})([$]?\d{1,7})$/", $pCoordinateString, $matches)) {
588 168
            return [$matches[1], $matches[2]];
589 3
        } elseif ((strpos($pCoordinateString, ':') !== false) || (strpos($pCoordinateString, ',') !== false)) {
590 1
            throw new Exception('Cell coordinate string can not be a range of cells');
591 2
        } elseif ($pCoordinateString == '') {
592 1
            throw new Exception('Cell coordinate can not be zero-length string');
593
        }
594
595 1
        throw new Exception('Invalid cell coordinate ' . $pCoordinateString);
596
    }
597
598
    /**
599
     * Make string row, column or cell coordinate absolute.
600
     *
601
     * @param string $pCoordinateString e.g. 'A' or '1' or 'A1'
602
     *                    Note that this value can be a row or column reference as well as a cell reference
603
     *
604
     * @throws Exception
605
     *
606
     * @return string Absolute coordinate        e.g. '$A' or '$1' or '$A$1'
607
     */
608 21
    public static function absoluteReference($pCoordinateString)
609
    {
610 21
        if (strpos($pCoordinateString, ':') === false && strpos($pCoordinateString, ',') === false) {
611
            // Split out any worksheet name from the reference
612 20
            $worksheet = '';
613 20
            $cellAddress = explode('!', $pCoordinateString);
614 20
            if (count($cellAddress) > 1) {
615 8
                list($worksheet, $pCoordinateString) = $cellAddress;
616
            }
617 20
            if ($worksheet > '') {
618 8
                $worksheet .= '!';
619
            }
620
621
            // Create absolute coordinate
622 20
            if (ctype_digit($pCoordinateString)) {
623 2
                return $worksheet . '$' . $pCoordinateString;
624 18
            } elseif (ctype_alpha($pCoordinateString)) {
625 2
                return $worksheet . '$' . strtoupper($pCoordinateString);
626
            }
627
628 16
            return $worksheet . self::absoluteCoordinate($pCoordinateString);
629
        }
630
631 1
        throw new Exception('Cell coordinate string can not be a range of cells');
632
    }
633
634
    /**
635
     * Make string coordinate absolute.
636
     *
637
     * @param string $pCoordinateString e.g. 'A1'
638
     *
639
     * @throws Exception
640
     *
641
     * @return string Absolute coordinate        e.g. '$A$1'
642
     */
643 32
    public static function absoluteCoordinate($pCoordinateString)
644
    {
645 32
        if (strpos($pCoordinateString, ':') === false && strpos($pCoordinateString, ',') === false) {
646
            // Split out any worksheet name from the coordinate
647 31
            $worksheet = '';
648 31
            $cellAddress = explode('!', $pCoordinateString);
649 31
            if (count($cellAddress) > 1) {
650 6
                list($worksheet, $pCoordinateString) = $cellAddress;
651
            }
652 31
            if ($worksheet > '') {
653 6
                $worksheet .= '!';
654
            }
655
656
            // Create absolute coordinate
657 31
            list($column, $row) = self::coordinateFromString($pCoordinateString);
658 31
            $column = ltrim($column, '$');
659 31
            $row = ltrim($row, '$');
660
661 31
            return $worksheet . '$' . $column . '$' . $row;
662
        }
663
664 1
        throw new Exception('Cell coordinate string can not be a range of cells');
665
    }
666
667
    /**
668
     * Split range into coordinate strings.
669
     *
670
     * @param string $pRange e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4'
671
     *
672
     * @return array Array containg one or more arrays containing one or two coordinate strings
673
     *                                e.g. array('B4','D9') or array(array('B4','D9'),array('H2','O11'))
674
     *                                        or array('B4')
675
     */
676 103
    public static function splitRange($pRange)
677
    {
678
        // Ensure $pRange is a valid range
679 103
        if (empty($pRange)) {
680
            $pRange = self::DEFAULT_RANGE;
681
        }
682
683 103
        $exploded = explode(',', $pRange);
684 103
        $counter = count($exploded);
685 103
        for ($i = 0; $i < $counter; ++$i) {
686 103
            $exploded[$i] = explode(':', $exploded[$i]);
687
        }
688
689 103
        return $exploded;
690
    }
691
692
    /**
693
     * Build range from coordinate strings.
694
     *
695
     * @param array $pRange Array containg one or more arrays containing one or two coordinate strings
696
     *
697
     * @throws Exception
698
     *
699
     * @return string String representation of $pRange
700
     */
701 19
    public static function buildRange(array $pRange)
702
    {
703
        // Verify range
704 19
        if (empty($pRange) || !is_array($pRange[0])) {
705
            throw new Exception('Range does not contain any information');
706
        }
707
708
        // Build range
709 19
        $imploded = [];
0 ignored issues
show
Unused Code introduced by
$imploded is not used, you could remove the assignment.

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

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

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

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

Loading history...
710 19
        $counter = count($pRange);
711 19
        for ($i = 0; $i < $counter; ++$i) {
712 19
            $pRange[$i] = implode(':', $pRange[$i]);
713
        }
714 19
        $imploded = implode(',', $pRange);
715
716 19
        return $imploded;
717
    }
718
719
    /**
720
     * Calculate range boundaries.
721
     *
722
     * @param string $pRange Cell range (e.g. A1:A1)
723
     *
724
     * @return array Range coordinates array(Start Cell, End Cell)
725
     *                    where Start Cell and End Cell are arrays (Column Number, Row Number)
726
     */
727 65
    public static function rangeBoundaries($pRange)
728
    {
729
        // Ensure $pRange is a valid range
730 65
        if (empty($pRange)) {
731
            $pRange = self::DEFAULT_RANGE;
732
        }
733
734
        // Uppercase coordinate
735 65
        $pRange = strtoupper($pRange);
736
737
        // Extract range
738 65 View Code Duplication
        if (strpos($pRange, ':') === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
739 2
            $rangeA = $rangeB = $pRange;
740
        } else {
741 63
            list($rangeA, $rangeB) = explode(':', $pRange);
742
        }
743
744
        // Calculate range outer borders
745 65
        $rangeStart = self::coordinateFromString($rangeA);
746 65
        $rangeEnd = self::coordinateFromString($rangeB);
747
748
        // Translate column into index
749 65
        $rangeStart[0] = self::columnIndexFromString($rangeStart[0]);
750 65
        $rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]);
751
752 65
        return [$rangeStart, $rangeEnd];
753
    }
754
755
    /**
756
     * Calculate range dimension.
757
     *
758
     * @param string $pRange Cell range (e.g. A1:A1)
759
     *
760
     * @return array Range dimension (width, height)
761
     */
762 16
    public static function rangeDimension($pRange)
763
    {
764
        // Calculate range outer borders
765 16
        list($rangeStart, $rangeEnd) = self::rangeBoundaries($pRange);
766
767 16
        return [($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1)];
768
    }
769
770
    /**
771
     * Calculate range boundaries.
772
     *
773
     * @param string $pRange Cell range (e.g. A1:A1)
774
     *
775
     * @return array Range coordinates array(Start Cell, End Cell)
776
     *                    where Start Cell and End Cell are arrays (Column ID, Row Number)
777
     */
778 13
    public static function getRangeBoundaries($pRange)
779
    {
780
        // Ensure $pRange is a valid range
781 13
        if (empty($pRange)) {
782
            $pRange = self::DEFAULT_RANGE;
783
        }
784
785
        // Uppercase coordinate
786 13
        $pRange = strtoupper($pRange);
787
788
        // Extract range
789 13 View Code Duplication
        if (strpos($pRange, ':') === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
790 1
            $rangeA = $rangeB = $pRange;
791
        } else {
792 12
            list($rangeA, $rangeB) = explode(':', $pRange);
793
        }
794
795 13
        return [self::coordinateFromString($rangeA), self::coordinateFromString($rangeB)];
796
    }
797
798
    /**
799
     * Column index from string.
800
     *
801
     * @param string $pString eg 'A'
802
     *
803
     * @return int Column index (base 1 !!!)
804
     */
805 163
    public static function columnIndexFromString($pString)
806
    {
807
        //    Using a lookup cache adds a slight memory overhead, but boosts speed
808
        //    caching using a static within the method is faster than a class static,
809
        //        though it's additional memory overhead
810 163
        static $indexCache = [];
811
812 163
        if (isset($indexCache[$pString])) {
813 148
            return $indexCache[$pString];
814
        }
815
        //    It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array rather than use ord()
816
        //        and make it case insensitive to get rid of the strtoupper() as well. Because it's a static, there's no significant
817
        //        memory overhead either
818 118
        static $columnLookup = [
819
            'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10, 'K' => 11, 'L' => 12, 'M' => 13,
820
            'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19, 'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26,
821
            'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10, 'k' => 11, 'l' => 12, 'm' => 13,
822
            'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19, 't' => 20, 'u' => 21, 'v' => 22, 'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26,
823
        ];
824
825
        //    We also use the language construct isset() rather than the more costly strlen() function to match the length of $pString
826
        //        for improved performance
827 118
        if (isset($pString[0])) {
828 116
            if (!isset($pString[1])) {
829 105
                $indexCache[$pString] = $columnLookup[$pString];
830
831 105
                return $indexCache[$pString];
832 12
            } elseif (!isset($pString[2])) {
833 9
                $indexCache[$pString] = $columnLookup[$pString[0]] * 26 + $columnLookup[$pString[1]];
834
835 9
                return $indexCache[$pString];
836 3
            } elseif (!isset($pString[3])) {
837 2
                $indexCache[$pString] = $columnLookup[$pString[0]] * 676 + $columnLookup[$pString[1]] * 26 + $columnLookup[$pString[2]];
838
839 2
                return $indexCache[$pString];
840
            }
841
        }
842
843 3
        throw new Exception('Column string index can not be ' . ((isset($pString[0])) ? 'longer than 3 characters' : 'empty'));
844
    }
845
846
    /**
847
     * String from columnindex.
848
     *
849
     * @param int $columnIndex Column index (A = 0)
850
     *
851
     * @return string
852
     */
853 132
    public static function stringFromColumnIndex($columnIndex)
854
    {
855 132
        static $indexCache = [];
856
857 132
        if (!isset($indexCache[$columnIndex])) {
858 113
            $indexValue = $columnIndex + 1;
859 113
            $base26 = null;
860
            do {
861 113
                $characterValue = ($indexValue % 26) ?: 26;
862 113
                $indexValue = ($indexValue - $characterValue) / 26;
863 113
                $base26 = chr($characterValue + 64) . ($base26 ?: '');
864 113
            } while ($indexValue > 0);
865 113
            $indexCache[$columnIndex] = $base26;
866
        }
867
868 132
        return $indexCache[$columnIndex];
869
    }
870
871
    /**
872
     * Extract all cell references in range.
873
     *
874
     * @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25)
875
     *
876
     * @return array Array containing single cell references
877
     */
878 75
    public static function extractAllCellReferencesInRange($pRange)
879
    {
880
        // Returnvalue
881 75
        $returnValue = [];
882
883
        // Explode spaces
884 75
        $cellBlocks = explode(' ', str_replace('$', '', strtoupper($pRange)));
885 75
        foreach ($cellBlocks as $cellBlock) {
886
            // Single cell?
887 75
            if (strpos($cellBlock, ':') === false && strpos($cellBlock, ',') === false) {
888 60
                $returnValue[] = $cellBlock;
889
890 60
                continue;
891
            }
892
893
            // Range...
894 67
            $ranges = self::splitRange($cellBlock);
895 67
            foreach ($ranges as $range) {
896
                // Single cell?
897 67
                if (!isset($range[1])) {
898
                    $returnValue[] = $range[0];
899
900
                    continue;
901
                }
902
903
                // Range...
904 67
                list($rangeStart, $rangeEnd) = $range;
905 67
                sscanf($rangeStart, '%[A-Z]%d', $startCol, $startRow);
0 ignored issues
show
Bug introduced by
The variable $startRow does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
906 67
                sscanf($rangeEnd, '%[A-Z]%d', $endCol, $endRow);
0 ignored issues
show
Bug introduced by
The variable $endRow does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
907 67
                ++$endCol;
908
909
                // Current data
910 67
                $currentCol = $startCol;
911 67
                $currentRow = $startRow;
912
913
                // Loop cells
914 67
                while ($currentCol != $endCol) {
915 67
                    while ($currentRow <= $endRow) {
916 67
                        $returnValue[] = $currentCol . $currentRow;
917 67
                        ++$currentRow;
918
                    }
919 67
                    ++$currentCol;
920 67
                    $currentRow = $startRow;
921
                }
922
            }
923
        }
924
925
        //    Sort the result by column and row
926 75
        $sortKeys = [];
927 75
        foreach (array_unique($returnValue) as $coord) {
928 75
            sscanf($coord, '%[A-Z]%d', $column, $row);
0 ignored issues
show
Bug introduced by
The variable $row does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
929 75
            $sortKeys[sprintf('%3s%09d', $column, $row)] = $coord;
930
        }
931 75
        ksort($sortKeys);
932
933
        // Return value
934 75
        return array_values($sortKeys);
935
    }
936
937
    /**
938
     * Convert an associative array of single cell coordinates to values to an associative array
939
     * of cell ranges to values.  Only adjacent cell coordinates with the same
940
     * value will be merged.  If the value is an object, it must implement the method getHashCode().
941
     *
942
     * For example, this function converts:
943
     *
944
     *    [ 'A1' => 'x', 'A2' => 'x', 'A3' => 'x', 'A4' => 'y' ]
945
     *
946
     * to:
947
     *
948
     *    [ 'A1:A3' => 'x', 'A4' => 'y' ]
949
     *
950
     * @param array $pCoordCollection associative array mapping coordinates to values
951
     *
952
     * @return array associative array mapping coordinate ranges to valuea
953
     */
954 6
    public static function mergeRangesInCollection(array $pCoordCollection)
955
    {
956 6
        $hashedValues = [];
957
958 6
        foreach ($pCoordCollection as $coord => $value) {
959 6
            list($column, $row) = self::coordinateFromString($coord);
960 6
            $row = (int) (ltrim($row, '$'));
961 6
            $hashCode = $column . '-' . (is_object($value) ? $value->getHashCode() : $value);
962
963 6
            if (!isset($hashedValues[$hashCode])) {
964 6
                $hashedValues[$hashCode] = (object) [
965 6
                    'value' => $value,
966 6
                    'col' => $column,
967 6
                    'rows' => [$row],
968
                ];
969
            } else {
970 6
                $hashedValues[$hashCode]->rows[] = $row;
971
            }
972
        }
973
974 6
        $mergedCoordCollection = [];
975 6
        ksort($hashedValues);
976
977 6
        foreach ($hashedValues as $hashedValue) {
978 6
            sort($hashedValue->rows);
979 6
            $rowStart = null;
980 6
            $rowEnd = null;
981 6
            $ranges = [];
982
983 6
            foreach ($hashedValue->rows as $row) {
984 6
                if ($rowStart === null) {
985 6
                    $rowStart = $row;
986 6
                    $rowEnd = $row;
987 3
                } elseif ($rowEnd === $row - 1) {
988 3
                    $rowEnd = $row;
989
                } else {
990 1 View Code Duplication
                    if ($rowStart == $rowEnd) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
991
                        $ranges[] = $hashedValue->col . $rowStart;
992
                    } else {
993 1
                        $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd;
994
                    }
995
996 1
                    $rowStart = $row;
997 6
                    $rowEnd = $row;
998
                }
999
            }
1000
1001 6 View Code Duplication
            if ($rowStart !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1002 6
                if ($rowStart == $rowEnd) {
1003 5
                    $ranges[] = $hashedValue->col . $rowStart;
1004
                } else {
1005 2
                    $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd;
1006
                }
1007
            }
1008
1009 6
            foreach ($ranges as $range) {
1010 6
                $mergedCoordCollection[$range] = $hashedValue->value;
1011
            }
1012
        }
1013
1014 6
        return $mergedCoordCollection;
1015
    }
1016
1017
    /**
1018
     * Compare 2 cells.
1019
     *
1020
     * @param Cell $a Cell a
1021
     * @param Cell $b Cell b
1022
     *
1023
     * @return int Result of comparison (always -1 or 1, never zero!)
1024
     */
1025
    public static function compareCells(Cell $a, Cell $b)
1026
    {
1027
        if ($a->getRow() < $b->getRow()) {
1028
            return -1;
1029
        } elseif ($a->getRow() > $b->getRow()) {
1030
            return 1;
1031
        } elseif (self::columnIndexFromString($a->getColumn()) < self::columnIndexFromString($b->getColumn())) {
1032
            return -1;
1033
        }
1034
1035
        return 1;
1036
    }
1037
1038
    /**
1039
     * Get value binder to use.
1040
     *
1041
     * @return IValueBinder
1042
     */
1043 78
    public static function getValueBinder()
1044
    {
1045 78
        if (self::$valueBinder === null) {
1046 71
            self::$valueBinder = new DefaultValueBinder();
1047
        }
1048
1049 78
        return self::$valueBinder;
1050
    }
1051
1052
    /**
1053
     * Set value binder to use.
1054
     *
1055
     * @param IValueBinder $binder
1056
     *
1057
     * @throws Exception
1058
     */
1059 2
    public static function setValueBinder(IValueBinder $binder)
1060
    {
1061 2
        self::$valueBinder = $binder;
1062 2
    }
1063
1064
    /**
1065
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
1066
     */
1067 2 View Code Duplication
    public function __clone()
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...
1068
    {
1069 2
        $vars = get_object_vars($this);
1070 2
        foreach ($vars as $key => $value) {
1071 2
            if ((is_object($value)) && ($key != 'parent')) {
1072
                $this->$key = clone $value;
1073
            } else {
1074 2
                $this->$key = $value;
1075
            }
1076
        }
1077 2
    }
1078
1079
    /**
1080
     * Get index to cellXf.
1081
     *
1082
     * @return int
1083
     */
1084 99
    public function getXfIndex()
1085
    {
1086 99
        return $this->xfIndex;
1087
    }
1088
1089
    /**
1090
     * Set index to cellXf.
1091
     *
1092
     * @param int $pValue
1093
     *
1094
     * @return Cell
1095
     */
1096 92
    public function setXfIndex($pValue)
1097
    {
1098 92
        $this->xfIndex = $pValue;
1099
1100 92
        return $this->updateInCollection();
1101
    }
1102
1103
    /**
1104
     * Set the formula attributes.
1105
     *
1106
     * @param mixed $pAttributes
1107
     */
1108
    public function setFormulaAttributes($pAttributes)
1109
    {
1110
        $this->formulaAttributes = $pAttributes;
1111
1112
        return $this;
1113
    }
1114
1115
    /**
1116
     * Get the formula attributes.
1117
     */
1118 18
    public function getFormulaAttributes()
1119
    {
1120 18
        return $this->formulaAttributes;
1121
    }
1122
1123
    /**
1124
     * Convert to string.
1125
     *
1126
     * @return string
1127
     */
1128
    public function __toString()
1129
    {
1130
        return (string) $this->getValue();
1131
    }
1132
}
1133