Completed
Push — develop ( fe73b2...9e8356 )
by Adrien
18:16
created

Cell   F

Complexity

Total Complexity 136

Size/Duplication

Total Lines 1042
Duplicated Lines 2.88 %

Coupling/Cohesion

Components 2
Dependencies 11

Test Coverage

Coverage 72.5%

Importance

Changes 0
Metric Value
dl 30
loc 1042
ccs 232
cts 320
cp 0.725
rs 3.9999
c 0
b 0
f 0
wmc 136
lcom 2
cbo 11

51 Methods

Rating   Name   Duplication   Size   Complexity  
A notifyCacheController() 0 6 1
A detach() 0 4 1
A attach() 0 4 1
A __construct() 0 18 4
A getColumn() 0 4 1
A getRow() 0 4 1
A getCoordinate() 0 4 1
A getValue() 0 4 1
A getFormattedValue() 0 8 1
A setValue() 0 8 2
D setValueExplicit() 0 38 9
A setCalculatedValue() 0 8 3
A getOldCalculatedValue() 0 4 1
A getDataType() 0 4 1
A setDataType() 0 9 2
A isFormula() 0 4 1
A hasDataValidation() 0 8 2
A getDataValidation() 0 8 2
A setDataValidation() 10 10 2
A hasHyperlink() 0 8 2
A getHyperlink() 0 8 2
A setHyperlink() 10 10 2
A getParent() 0 4 1
A getWorksheet() 0 4 1
A isInMergeRange() 0 4 1
A isMergeRangeValueCell() 0 12 3
A getMergeRange() 0 10 3
A getStyle() 0 4 1
A rebindParent() 0 6 1
A isInRange() 0 12 4
B coordinateFromString() 0 12 5
C absoluteReference() 0 25 7
B absoluteCoordinate() 0 23 5
A splitRange() 0 15 3
B buildRange() 0 17 5
B rangeBoundaries() 5 27 3
A rangeDimension() 0 7 1
A getRangeBoundaries() 5 19 3
C columnIndexFromString() 0 39 7
B stringFromColumnIndex() 0 23 4
B extractAllCellReferencesInRange() 0 56 9
A compareCells() 0 12 4
A getValueBinder() 0 8 2
A setValueBinder() 0 8 2
A __clone() 0 11 4
A getXfIndex() 0 4 1
A setXfIndex() 0 6 1
A __toString() 0 4 1
D getCalculatedValue() 0 34 9
A setFormulaAttributes() 0 6 1
A getFormulaAttributes() 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 Cell 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 Cell, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet;
4
5
/**
6
 *    Copyright (c) 2006 - 2016 PhpSpreadsheet.
7
 *
8
 *    This library is free software; you can redistribute it and/or
9
 *    modify it under the terms of the GNU Lesser General Public
10
 *    License as published by the Free Software Foundation; either
11
 *    version 2.1 of the License, or (at your option) any later version.
12
 *
13
 *    This library is distributed in the hope that it will be useful,
14
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
 *    Lesser General Public License for more details.
17
 *
18
 *    You should have received a copy of the GNU Lesser General Public
19
 *    License along with this library; if not, write to the Free Software
20
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
 *
22
 *    @category    PhpSpreadsheet
23
 *
24
 *    @copyright    Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
25
 *    @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
26
 */
27
class Cell
28
{
29
    /**
30
     * Default range variable constant.
31
     *
32
     * @var string
33
     */
34
    const DEFAULT_RANGE = 'A1:A1';
35
36
    /**
37
     * Value binder to use.
38
     *
39
     * @var Cell\IValueBinder
40
     */
41
    private static $valueBinder;
42
43
    /**
44
     * Value of the cell.
45
     *
46
     * @var mixed
47
     */
48
    private $value;
49
50
    /**
51
     *    Calculated value of the cell (used for caching)
52
     *    This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
53
     *        create the original spreadsheet file.
54
     *    Note that this value is not guaranteed to reflect the actual calculated value because it is
55
     *        possible that auto-calculation was disabled in the original spreadsheet, and underlying data
56
     *        values used by the formula have changed since it was last calculated.
57
     *
58
     * @var mixed
59
     */
60
    private $calculatedValue;
61
62
    /**
63
     * Type of the cell data.
64
     *
65
     * @var string
66
     */
67
    private $dataType;
68
69
    /**
70
     * Parent worksheet.
71
     *
72
     * @var CachedObjectStorage\CacheBase
73
     */
74
    private $parent;
75
76
    /**
77
     * Index to cellXf.
78
     *
79
     * @var int
80
     */
81
    private $xfIndex = 0;
82
83
    /**
84
     * Attributes of the formula.
85
     */
86
    private $formulaAttributes;
87
88
    /**
89
     * Send notification to the cache controller.
90
     **/
91 73
    public function notifyCacheController()
92
    {
93 73
        $this->parent->updateCacheData($this);
94
95 73
        return $this;
96
    }
97
98 17
    public function detach()
99
    {
100 17
        $this->parent = null;
101 17
    }
102
103 37
    public function attach(CachedObjectStorage\CacheBase $parent)
104
    {
105 37
        $this->parent = $parent;
106 37
    }
107
108
    /**
109
     * Create a new Cell.
110
     *
111
     * @param mixed $pValue
112
     * @param string $pDataType
113
     * @param Worksheet $pSheet
114
     *
115
     * @throws Exception
116
     */
117 73
    public function __construct($pValue = null, $pDataType = null, Worksheet $pSheet = null)
118
    {
119
        // Initialise cell value
120 73
        $this->value = $pValue;
121
122
        // Set worksheet cache
123 73
        $this->parent = $pSheet->getCellCacheController();
0 ignored issues
show
Bug introduced by
It seems like $pSheet is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
124
125
        // Set datatype?
126 73
        if ($pDataType !== null) {
127 73
            if ($pDataType == Cell\DataType::TYPE_STRING2) {
128
                $pDataType = Cell\DataType::TYPE_STRING;
129
            }
130 73
            $this->dataType = $pDataType;
131
        } elseif (!self::getValueBinder()->bindValue($this, $pValue)) {
132
            throw new Exception('Value could not be bound to cell.');
133
        }
134 73
    }
135
136
    /**
137
     * Get cell coordinate column.
138
     *
139
     * @return string
140
     */
141 45
    public function getColumn()
142
    {
143 45
        return $this->parent->getCurrentColumn();
144
    }
145
146
    /**
147
     * Get cell coordinate row.
148
     *
149
     * @return int
150
     */
151 45
    public function getRow()
152
    {
153 45
        return $this->parent->getCurrentRow();
154
    }
155
156
    /**
157
     * Get cell coordinate.
158
     *
159
     * @return string
160
     */
161 73
    public function getCoordinate()
162
    {
163 73
        return $this->parent->getCurrentAddress();
164
    }
165
166
    /**
167
     * Get cell value.
168
     *
169
     * @return mixed
170
     */
171 72
    public function getValue()
172
    {
173 72
        return $this->value;
174
    }
175
176
    /**
177
     * Get cell value with formatting.
178
     *
179
     * @return string
180
     */
181 1
    public function getFormattedValue()
182
    {
183 1
        return (string) Style\NumberFormat::toFormattedString(
184 1
            $this->getCalculatedValue(),
185 1
            $this->getStyle()
186 1
                ->getNumberFormat()->getFormatCode()
187
        );
188
    }
189
190
    /**
191
     * Set cell value.
192
     *
193
     *    Sets the value for a cell, automatically determining the datatype using the value binder
194
     *
195
     * @param mixed $pValue Value
196
     *
197
     * @throws Exception
198
     *
199
     * @return Cell
200
     */
201 55
    public function setValue($pValue = null)
202
    {
203 55
        if (!self::getValueBinder()->bindValue($this, $pValue)) {
204
            throw new Exception('Value could not be bound to cell.');
205
        }
206
207 55
        return $this;
208
    }
209
210
    /**
211
     * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder).
212
     *
213
     * @param mixed $pValue Value
214
     * @param string $pDataType Explicit data type
215
     *
216
     * @throws Exception
217
     *
218
     * @return Cell
219
     */
220 70
    public function setValueExplicit($pValue = null, $pDataType = Cell\DataType::TYPE_STRING)
221
    {
222
        // set the value according to data type
223
        switch ($pDataType) {
224 70
            case Cell\DataType::TYPE_NULL:
225 18
                $this->value = $pValue;
226 18
                break;
227 70
            case Cell\DataType::TYPE_STRING2:
228
                $pDataType = Cell\DataType::TYPE_STRING;
229
                // no break
230 70
            case Cell\DataType::TYPE_STRING:
231
                // Synonym for string
232 60
            case Cell\DataType::TYPE_INLINE:
233
                // Rich text
234 59
                $this->value = Cell\DataType::checkString($pValue);
235 59
                break;
236 59
            case Cell\DataType::TYPE_NUMERIC:
237 55
                $this->value = (float) $pValue;
238 55
                break;
239 28
            case Cell\DataType::TYPE_FORMULA:
240 26
                $this->value = (string) $pValue;
241 26
                break;
242 9
            case Cell\DataType::TYPE_BOOL:
243 9
                $this->value = (bool) $pValue;
244 9
                break;
245
            case Cell\DataType::TYPE_ERROR:
246
                $this->value = Cell\DataType::checkErrorCode($pValue);
247
                break;
248
            default:
249
                throw new Exception('Invalid datatype: ' . $pDataType);
250
                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...
251
        }
252
253
        // set the datatype
254 70
        $this->dataType = $pDataType;
255
256 70
        return $this->notifyCacheController();
257
    }
258
259
    /**
260
     * Get calculated cell value.
261
     *
262
     *    @deprecated        Since version 1.7.8 for planned changes to cell for array formula handling
263
     *
264
     * @param bool $resetLog Whether the calculation engine logger should be reset or not
265
     *
266
     * @throws Exception
267
     *
268
     * @return mixed
269
     */
270 42
    public function getCalculatedValue($resetLog = true)
271
    {
272 42
        if ($this->dataType == Cell\DataType::TYPE_FORMULA) {
273
            try {
274 25
                $result = Calculation::getInstance(
275 25
                    $this->getWorksheet()->getParent()
276 25
                )->calculateCellValue($this, $resetLog);
277
                //    We don't yet handle array returns
278 25
                if (is_array($result)) {
279 25
                    while (is_array($result)) {
280 6
                        $result = array_pop($result);
281
                    }
282
                }
283
            } catch (Exception $ex) {
284
                if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
285
                    return $this->calculatedValue; // Fallback for calculations referencing external files.
286
                }
287
                $result = '#N/A';
0 ignored issues
show
Unused Code introduced by
$result 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...
288
                throw new Calculation\Exception(
289
                    $this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage()
290
                );
291
            }
292
293 25
            if ($result === '#Not Yet Implemented') {
294
                return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
295
            }
296
297 25
            return $result;
298 42
        } elseif ($this->value instanceof RichText) {
299 2
            return $this->value->getPlainText();
300
        }
301
302 42
        return $this->value;
303
    }
304
305
    /**
306
     * Set old calculated value (cached).
307
     *
308
     * @param mixed $pValue Value
309
     *
310
     * @return Cell
311
     */
312 8
    public function setCalculatedValue($pValue = null)
313
    {
314 8
        if ($pValue !== null) {
315 8
            $this->calculatedValue = (is_numeric($pValue)) ? (float) $pValue : $pValue;
316
        }
317
318 8
        return $this->notifyCacheController();
319
    }
320
321
    /**
322
     *    Get old calculated value (cached)
323
     *    This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
324
     *        create the original spreadsheet file.
325
     *    Note that this value is not guaranteed to refelect the actual calculated value because it is
326
     *        possible that auto-calculation was disabled in the original spreadsheet, and underlying data
327
     *        values used by the formula have changed since it was last calculated.
328
     *
329
     * @return mixed
330
     */
331
    public function getOldCalculatedValue()
332
    {
333
        return $this->calculatedValue;
334
    }
335
336
    /**
337
     * Get cell data type.
338
     *
339
     * @return string
340
     */
341 62
    public function getDataType()
342
    {
343 62
        return $this->dataType;
344
    }
345
346
    /**
347
     * Set cell data type.
348
     *
349
     * @param string $pDataType
350
     *
351
     * @return Cell
352
     */
353
    public function setDataType($pDataType = Cell\DataType::TYPE_STRING)
354
    {
355
        if ($pDataType == Cell\DataType::TYPE_STRING2) {
356
            $pDataType = Cell\DataType::TYPE_STRING;
357
        }
358
        $this->dataType = $pDataType;
359
360
        return $this->notifyCacheController();
361
    }
362
363
    /**
364
     * Identify if the cell contains a formula.
365
     *
366
     * @return bool
367
     */
368
    public function isFormula()
369
    {
370
        return $this->dataType == Cell\DataType::TYPE_FORMULA;
371
    }
372
373
    /**
374
     *    Does this cell contain Data validation rules?
375
     *
376
     * @throws Exception
377
     *
378
     * @return bool
379
     */
380
    public function hasDataValidation()
381
    {
382
        if (!isset($this->parent)) {
383
            throw new Exception('Cannot check for data validation when cell is not bound to a worksheet');
384
        }
385
386
        return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
387
    }
388
389
    /**
390
     * Get Data validation rules.
391
     *
392
     * @throws Exception
393
     *
394
     * @return Cell\DataValidation
395
     */
396 2
    public function getDataValidation()
397
    {
398 2
        if (!isset($this->parent)) {
399
            throw new Exception('Cannot get data validation for cell that is not bound to a worksheet');
400
        }
401
402 2
        return $this->getWorksheet()->getDataValidation($this->getCoordinate());
403
    }
404
405
    /**
406
     * Set Data validation rules.
407
     *
408
     * @param Cell\DataValidation $pDataValidation
409
     *
410
     * @throws Exception
411
     *
412
     * @return Cell
413
     */
414 View Code Duplication
    public function setDataValidation(Cell\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...
415
    {
416
        if (!isset($this->parent)) {
417
            throw new Exception('Cannot set data validation for cell that is not bound to a worksheet');
418
        }
419
420
        $this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation);
421
422
        return $this->notifyCacheController();
423
    }
424
425
    /**
426
     *    Does this cell contain a Hyperlink?
427
     *
428
     * @throws Exception
429
     *
430
     * @return bool
431
     */
432
    public function hasHyperlink()
433
    {
434
        if (!isset($this->parent)) {
435
            throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet');
436
        }
437
438
        return $this->getWorksheet()->hyperlinkExists($this->getCoordinate());
439
    }
440
441
    /**
442
     * Get Hyperlink.
443
     *
444
     * @throws Exception
445
     *
446
     * @return Cell\Hyperlink
447
     */
448 14
    public function getHyperlink()
449
    {
450 14
        if (!isset($this->parent)) {
451
            throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
452
        }
453
454 14
        return $this->getWorksheet()->getHyperlink($this->getCoordinate());
455
    }
456
457
    /**
458
     * Set Hyperlink.
459
     *
460
     * @param Cell\Hyperlink $pHyperlink
461
     *
462
     * @throws Exception
463
     *
464
     * @return Cell
465
     */
466 View Code Duplication
    public function setHyperlink(Cell\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...
467
    {
468
        if (!isset($this->parent)) {
469
            throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
470
        }
471
472
        $this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink);
473
474
        return $this->notifyCacheController();
475
    }
476
477
    /**
478
     * Get parent worksheet.
479
     *
480
     * @return CachedObjectStorage\CacheBase
481
     */
482 39
    public function getParent()
483
    {
484 39
        return $this->parent;
485
    }
486
487
    /**
488
     * Get parent worksheet.
489
     *
490
     * @return Worksheet
491
     */
492 48
    public function getWorksheet()
493
    {
494 48
        return $this->parent->getParent();
495
    }
496
497
    /**
498
     * Is this cell in a merge range.
499
     *
500
     * @return bool
501
     */
502
    public function isInMergeRange()
503
    {
504
        return (bool) $this->getMergeRange();
505
    }
506
507
    /**
508
     * Is this cell the master (top left cell) in a merge range (that holds the actual data value).
509
     *
510
     * @return bool
511
     */
512
    public function isMergeRangeValueCell()
513
    {
514
        if ($mergeRange = $this->getMergeRange()) {
515
            $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...
516
            list($startCell) = $mergeRange[0];
517
            if ($this->getCoordinate() === $startCell) {
518
                return true;
519
            }
520
        }
521
522
        return false;
523
    }
524
525
    /**
526
     * If this cell is in a merge range, then return the range.
527
     *
528
     * @return string
529
     */
530
    public function getMergeRange()
531
    {
532
        foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) {
533
            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...
534
                return $mergeRange;
535
            }
536
        }
537
538
        return false;
539
    }
540
541
    /**
542
     * Get cell style.
543
     *
544
     * @return Style
545
     */
546 1
    public function getStyle()
547
    {
548 1
        return $this->getWorksheet()->getStyle($this->getCoordinate());
549
    }
550
551
    /**
552
     * Re-bind parent.
553
     *
554
     * @param Worksheet $parent
555
     *
556
     * @return Cell
557
     */
558
    public function rebindParent(Worksheet $parent)
559
    {
560
        $this->parent = $parent->getCellCacheController();
0 ignored issues
show
Documentation Bug introduced by
It seems like $parent->getCellCacheController() of type object<PhpOffice\PhpSpre...achedObjectStorage_xxx> is incompatible with the declared type object<PhpOffice\PhpSpre...bjectStorage\CacheBase> of property $parent.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
561
562
        return $this->notifyCacheController();
563
    }
564
565
    /**
566
     *    Is cell in a specific range?
567
     *
568
     * @param string $pRange Cell range (e.g. A1:A1)
569
     *
570
     * @return bool
571
     */
572
    public function isInRange($pRange = 'A1:A1')
573
    {
574
        list($rangeStart, $rangeEnd) = self::rangeBoundaries($pRange);
575
576
        // Translate properties
577
        $myColumn = self::columnIndexFromString($this->getColumn());
578
        $myRow = $this->getRow();
579
580
        // Verify if cell is in range
581
        return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) &&
582
                ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow);
583
    }
584
585
    /**
586
     * Coordinate from string.
587
     *
588
     * @param string $pCoordinateString
589
     *
590
     * @throws Exception
591
     *
592
     * @return string[] Array containing column and row (indexes 0 and 1)
593
     */
594 118
    public static function coordinateFromString($pCoordinateString = 'A1')
595
    {
596 118
        if (preg_match("/^([$]?[A-Z]{1,3})([$]?\d{1,7})$/", $pCoordinateString, $matches)) {
597 115
            return [$matches[1], $matches[2]];
598 3
        } elseif ((strpos($pCoordinateString, ':') !== false) || (strpos($pCoordinateString, ',') !== false)) {
599 1
            throw new Exception('Cell coordinate string can not be a range of cells');
600 2
        } elseif ($pCoordinateString == '') {
601 1
            throw new Exception('Cell coordinate can not be zero-length string');
602
        }
603
604 1
        throw new Exception('Invalid cell coordinate ' . $pCoordinateString);
605
    }
606
607
    /**
608
     * Make string row, column or cell coordinate absolute.
609
     *
610
     * @param string $pCoordinateString e.g. 'A' or '1' or 'A1'
611
     *                    Note that this value can be a row or column reference as well as a cell reference
612
     *
613
     * @throws Exception
614
     *
615
     * @return string Absolute coordinate        e.g. '$A' or '$1' or '$A$1'
616
     */
617 21
    public static function absoluteReference($pCoordinateString = 'A1')
618
    {
619 21
        if (strpos($pCoordinateString, ':') === false && strpos($pCoordinateString, ',') === false) {
620
            // Split out any worksheet name from the reference
621 20
            $worksheet = '';
622 20
            $cellAddress = explode('!', $pCoordinateString);
623 20
            if (count($cellAddress) > 1) {
624 8
                list($worksheet, $pCoordinateString) = $cellAddress;
625
            }
626 20
            if ($worksheet > '') {
627 8
                $worksheet .= '!';
628
            }
629
630
            // Create absolute coordinate
631 20
            if (ctype_digit($pCoordinateString)) {
632 2
                return $worksheet . '$' . $pCoordinateString;
633 18
            } elseif (ctype_alpha($pCoordinateString)) {
634 2
                return $worksheet . '$' . strtoupper($pCoordinateString);
635
            }
636
637 16
            return $worksheet . self::absoluteCoordinate($pCoordinateString);
638
        }
639
640 1
        throw new Exception('Cell coordinate string can not be a range of cells');
641
    }
642
643
    /**
644
     * Make string coordinate absolute.
645
     *
646
     * @param string $pCoordinateString e.g. 'A1'
647
     *
648
     * @throws Exception
649
     *
650
     * @return string Absolute coordinate        e.g. '$A$1'
651
     */
652 32
    public static function absoluteCoordinate($pCoordinateString = 'A1')
653
    {
654 32
        if (strpos($pCoordinateString, ':') === false && strpos($pCoordinateString, ',') === false) {
655
            // Split out any worksheet name from the coordinate
656 31
            $worksheet = '';
657 31
            $cellAddress = explode('!', $pCoordinateString);
658 31
            if (count($cellAddress) > 1) {
659 6
                list($worksheet, $pCoordinateString) = $cellAddress;
660
            }
661 31
            if ($worksheet > '') {
662 6
                $worksheet .= '!';
663
            }
664
665
            // Create absolute coordinate
666 31
            list($column, $row) = self::coordinateFromString($pCoordinateString);
667 31
            $column = ltrim($column, '$');
668 31
            $row = ltrim($row, '$');
669
670 31
            return $worksheet . '$' . $column . '$' . $row;
671
        }
672
673 1
        throw new Exception('Cell coordinate string can not be a range of cells');
674
    }
675
676
    /**
677
     * Split range into coordinate strings.
678
     *
679
     * @param string $pRange e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4'
680
     *
681
     * @return array Array containg one or more arrays containing one or two coordinate strings
682
     *                                e.g. array('B4','D9') or array(array('B4','D9'),array('H2','O11'))
683
     *                                        or array('B4')
684
     */
685 72
    public static function splitRange($pRange = 'A1:A1')
686
    {
687
        // Ensure $pRange is a valid range
688 72
        if (empty($pRange)) {
689
            $pRange = self::DEFAULT_RANGE;
690
        }
691
692 72
        $exploded = explode(',', $pRange);
693 72
        $counter = count($exploded);
694 72
        for ($i = 0; $i < $counter; ++$i) {
695 72
            $exploded[$i] = explode(':', $exploded[$i]);
696
        }
697
698 72
        return $exploded;
699
    }
700
701
    /**
702
     * Build range from coordinate strings.
703
     *
704
     * @param array $pRange Array containg one or more arrays containing one or two coordinate strings
705
     *
706
     * @throws Exception
707
     *
708
     * @return string String representation of $pRange
709
     */
710 20
    public static function buildRange($pRange)
711
    {
712
        // Verify range
713 20
        if (!is_array($pRange) || empty($pRange) || !is_array($pRange[0])) {
714 1
            throw new Exception('Range does not contain any information');
715
        }
716
717
        // Build range
718 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...
719 19
        $counter = count($pRange);
720 19
        for ($i = 0; $i < $counter; ++$i) {
721 19
            $pRange[$i] = implode(':', $pRange[$i]);
722
        }
723 19
        $imploded = implode(',', $pRange);
724
725 19
        return $imploded;
726
    }
727
728
    /**
729
     * Calculate range boundaries.
730
     *
731
     * @param string $pRange Cell range (e.g. A1:A1)
732
     *
733
     * @return array Range coordinates array(Start Cell, End Cell)
734
     *                    where Start Cell and End Cell are arrays (Column Number, Row Number)
735
     */
736 41
    public static function rangeBoundaries($pRange = 'A1:A1')
737
    {
738
        // Ensure $pRange is a valid range
739 41
        if (empty($pRange)) {
740
            $pRange = self::DEFAULT_RANGE;
741
        }
742
743
        // Uppercase coordinate
744 41
        $pRange = strtoupper($pRange);
745
746
        // Extract range
747 41 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...
748 2
            $rangeA = $rangeB = $pRange;
749
        } else {
750 39
            list($rangeA, $rangeB) = explode(':', $pRange);
751
        }
752
753
        // Calculate range outer borders
754 41
        $rangeStart = self::coordinateFromString($rangeA);
755 41
        $rangeEnd = self::coordinateFromString($rangeB);
756
757
        // Translate column into index
758 41
        $rangeStart[0] = self::columnIndexFromString($rangeStart[0]);
759 41
        $rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]);
760
761 41
        return [$rangeStart, $rangeEnd];
762
    }
763
764
    /**
765
     * Calculate range dimension.
766
     *
767
     * @param string $pRange Cell range (e.g. A1:A1)
768
     *
769
     * @return array Range dimension (width, height)
770
     */
771 15
    public static function rangeDimension($pRange = 'A1:A1')
772
    {
773
        // Calculate range outer borders
774 15
        list($rangeStart, $rangeEnd) = self::rangeBoundaries($pRange);
775
776 15
        return [($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1)];
777
    }
778
779
    /**
780
     * Calculate range boundaries.
781
     *
782
     * @param string $pRange Cell range (e.g. A1:A1)
783
     *
784
     * @return array Range coordinates array(Start Cell, End Cell)
785
     *                    where Start Cell and End Cell are arrays (Column ID, Row Number)
786
     */
787 4
    public static function getRangeBoundaries($pRange = 'A1:A1')
788
    {
789
        // Ensure $pRange is a valid range
790 4
        if (empty($pRange)) {
791
            $pRange = self::DEFAULT_RANGE;
792
        }
793
794
        // Uppercase coordinate
795 4
        $pRange = strtoupper($pRange);
796
797
        // Extract range
798 4 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...
799 1
            $rangeA = $rangeB = $pRange;
800
        } else {
801 3
            list($rangeA, $rangeB) = explode(':', $pRange);
802
        }
803
804 4
        return [self::coordinateFromString($rangeA), self::coordinateFromString($rangeB)];
805
    }
806
807
    /**
808
     * Column index from string.
809
     *
810
     * @param string $pString
811
     *
812
     * @return int Column index (base 1 !!!)
813
     */
814 114
    public static function columnIndexFromString($pString = 'A')
815
    {
816
        //    Using a lookup cache adds a slight memory overhead, but boosts speed
817
        //    caching using a static within the method is faster than a class static,
818
        //        though it's additional memory overhead
819 114
        static $_indexCache = [];
820
821 114
        if (isset($_indexCache[$pString])) {
822 98
            return $_indexCache[$pString];
823
        }
824
        //    It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array rather than use ord()
825
        //        and make it case insensitive to get rid of the strtoupper() as well. Because it's a static, there's no significant
826
        //        memory overhead either
827 85
        static $_columnLookup = [
828
            '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,
829
            '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,
830
            '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,
831
            '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,
832
        ];
833
834
        //    We also use the language construct isset() rather than the more costly strlen() function to match the length of $pString
835
        //        for improved performance
836 85
        if (isset($pString[0])) {
837 83
            if (!isset($pString[1])) {
838 72
                $_indexCache[$pString] = $_columnLookup[$pString];
839
840 72
                return $_indexCache[$pString];
841 12
            } elseif (!isset($pString[2])) {
842 9
                $_indexCache[$pString] = $_columnLookup[$pString[0]] * 26 + $_columnLookup[$pString[1]];
843
844 9
                return $_indexCache[$pString];
845 3
            } elseif (!isset($pString[3])) {
846 2
                $_indexCache[$pString] = $_columnLookup[$pString[0]] * 676 + $_columnLookup[$pString[1]] * 26 + $_columnLookup[$pString[2]];
847
848 2
                return $_indexCache[$pString];
849
            }
850
        }
851 3
        throw new Exception('Column string index can not be ' . ((isset($pString[0])) ? 'longer than 3 characters' : 'empty'));
852
    }
853
854
    /**
855
     * String from columnindex.
856
     *
857
     * @param int $pColumnIndex Column index (base 0 !!!)
858
     *
859
     * @return string
860
     */
861 87
    public static function stringFromColumnIndex($pColumnIndex = 0)
862
    {
863
        //    Using a lookup cache adds a slight memory overhead, but boosts speed
864
        //    caching using a static within the method is faster than a class static,
865
        //        though it's additional memory overhead
866 87
        static $_indexCache = [];
867
868 87
        if (!isset($_indexCache[$pColumnIndex])) {
869
            // Determine column string
870 78
            if ($pColumnIndex < 26) {
871 68
                $_indexCache[$pColumnIndex] = chr(65 + $pColumnIndex);
872 12
            } elseif ($pColumnIndex < 702) {
873 9
                $_indexCache[$pColumnIndex] = chr(64 + ($pColumnIndex / 26)) .
874 9
                                                chr(65 + $pColumnIndex % 26);
875
            } else {
876 3
                $_indexCache[$pColumnIndex] = chr(64 + (($pColumnIndex - 26) / 676)) .
877 3
                                                chr(65 + ((($pColumnIndex - 26) % 676) / 26)) .
878 3
                                                chr(65 + $pColumnIndex % 26);
879
            }
880
        }
881
882 87
        return $_indexCache[$pColumnIndex];
883
    }
884
885
    /**
886
     * Extract all cell references in range.
887
     *
888
     * @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25)
889
     *
890
     * @return array Array containing single cell references
891
     */
892 46
    public static function extractAllCellReferencesInRange($pRange = 'A1')
893
    {
894
        // Returnvalue
895 46
        $returnValue = [];
896
897
        // Explode spaces
898 46
        $cellBlocks = explode(' ', str_replace('$', '', strtoupper($pRange)));
899 46
        foreach ($cellBlocks as $cellBlock) {
900
            // Single cell?
901 46
            if (strpos($cellBlock, ':') === false && strpos($cellBlock, ',') === false) {
902 37
                $returnValue[] = $cellBlock;
903 37
                continue;
904
            }
905
906
            // Range...
907 45
            $ranges = self::splitRange($cellBlock);
908 45
            foreach ($ranges as $range) {
909
                // Single cell?
910 45
                if (!isset($range[1])) {
911
                    $returnValue[] = $range[0];
912
                    continue;
913
                }
914
915
                // Range...
916 45
                list($rangeStart, $rangeEnd) = $range;
917 45
                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...
918 45
                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...
919 45
                ++$endCol;
920
921
                // Current data
922 45
                $currentCol = $startCol;
923 45
                $currentRow = $startRow;
924
925
                // Loop cells
926 45
                while ($currentCol != $endCol) {
927 45
                    while ($currentRow <= $endRow) {
928 45
                        $returnValue[] = $currentCol . $currentRow;
929 45
                        ++$currentRow;
930
                    }
931 45
                    ++$currentCol;
932 45
                    $currentRow = $startRow;
933
                }
934
            }
935
        }
936
937
        //    Sort the result by column and row
938 46
        $sortKeys = [];
939 46
        foreach (array_unique($returnValue) as $coord) {
940 46
            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...
941 46
            $sortKeys[sprintf('%3s%09d', $column, $row)] = $coord;
942
        }
943 46
        ksort($sortKeys);
944
945
        // Return value
946 46
        return array_values($sortKeys);
947
    }
948
949
    /**
950
     * Compare 2 cells.
951
     *
952
     * @param Cell $a Cell a
953
     * @param Cell $b Cell b
954
     *
955
     * @return int Result of comparison (always -1 or 1, never zero!)
956
     */
957
    public static function compareCells(Cell $a, Cell $b)
958
    {
959
        if ($a->getRow() < $b->getRow()) {
960
            return -1;
961
        } elseif ($a->getRow() > $b->getRow()) {
962
            return 1;
963
        } elseif (self::columnIndexFromString($a->getColumn()) < self::columnIndexFromString($b->getColumn())) {
964
            return -1;
965
        }
966
967
        return 1;
968
    }
969
970
    /**
971
     * Get value binder to use.
972
     *
973
     * @return Cell\IValueBinder
974
     */
975 55
    public static function getValueBinder()
976
    {
977 55
        if (self::$valueBinder === null) {
978 54
            self::$valueBinder = new Cell\DefaultValueBinder();
979
        }
980
981 55
        return self::$valueBinder;
982
    }
983
984
    /**
985
     * Set value binder to use.
986
     *
987
     * @param Cell\IValueBinder $binder
988
     *
989
     * @throws Exception
990
     */
991 1
    public static function setValueBinder(Cell\IValueBinder $binder = null)
992
    {
993 1
        if ($binder === null) {
994
            throw new Exception('A \\PhpOffice\\PhpSpreadsheet\\Cell\\IValueBinder is required for PhpSpreadsheet to function correctly.');
995
        }
996
997 1
        self::$valueBinder = $binder;
998 1
    }
999
1000
    /**
1001
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
1002
     */
1003 1
    public function __clone()
1004
    {
1005 1
        $vars = get_object_vars($this);
1006 1
        foreach ($vars as $key => $value) {
1007 1
            if ((is_object($value)) && ($key != 'parent')) {
1008
                $this->$key = clone $value;
1009
            } else {
1010 1
                $this->$key = $value;
1011
            }
1012
        }
1013 1
    }
1014
1015
    /**
1016
     * Get index to cellXf.
1017
     *
1018
     * @return int
1019
     */
1020 63
    public function getXfIndex()
1021
    {
1022 63
        return $this->xfIndex;
1023
    }
1024
1025
    /**
1026
     * Set index to cellXf.
1027
     *
1028
     * @param int $pValue
1029
     *
1030
     * @return Cell
1031
     */
1032 63
    public function setXfIndex($pValue = 0)
1033
    {
1034 63
        $this->xfIndex = $pValue;
1035
1036 63
        return $this->notifyCacheController();
1037
    }
1038
1039
    /**
1040
     *    @deprecated        Since version 1.7.8 for planned changes to cell for array formula handling
1041
     *
1042
     * @param mixed $pAttributes
1043
     */
1044
    public function setFormulaAttributes($pAttributes)
1045
    {
1046
        $this->formulaAttributes = $pAttributes;
1047
1048
        return $this;
1049
    }
1050
1051
    /**
1052
     *    @deprecated        Since version 1.7.8 for planned changes to cell for array formula handling
1053
     */
1054 18
    public function getFormulaAttributes()
1055
    {
1056 18
        return $this->formulaAttributes;
1057
    }
1058
1059
    /**
1060
     * Convert to string.
1061
     *
1062
     * @return string
1063
     */
1064
    public function __toString()
1065
    {
1066
        return (string) $this->getValue();
1067
    }
1068
}
1069