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\NumberFormat; |
10
|
|
|
use PhpOffice\PhpSpreadsheet\Style\Style; |
11
|
|
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; |
12
|
|
|
|
13
|
|
|
class Cell |
14
|
|
|
{ |
15
|
|
|
/** |
16
|
|
|
* Default range variable constant. |
17
|
|
|
* |
18
|
|
|
* @var string |
19
|
|
|
*/ |
20
|
|
|
const DEFAULT_RANGE = 'A1:A1'; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Value binder to use. |
24
|
|
|
* |
25
|
|
|
* @var IValueBinder |
26
|
|
|
*/ |
27
|
|
|
private static $valueBinder; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Value of the cell. |
31
|
|
|
* |
32
|
|
|
* @var mixed |
33
|
|
|
*/ |
34
|
|
|
private $value; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Calculated value of the cell (used for caching) |
38
|
|
|
* This returns the value last calculated by MS Excel or whichever spreadsheet program was used to |
39
|
|
|
* create the original spreadsheet file. |
40
|
|
|
* Note that this value is not guaranteed to reflect the actual calculated value because it is |
41
|
|
|
* possible that auto-calculation was disabled in the original spreadsheet, and underlying data |
42
|
|
|
* values used by the formula have changed since it was last calculated. |
43
|
|
|
* |
44
|
|
|
* @var mixed |
45
|
|
|
*/ |
46
|
|
|
private $calculatedValue; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Type of the cell data. |
50
|
|
|
* |
51
|
|
|
* @var string |
52
|
|
|
*/ |
53
|
|
|
private $dataType; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Collection of cells. |
57
|
|
|
* |
58
|
|
|
* @var Cells |
59
|
|
|
*/ |
60
|
|
|
private $parent; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Index to cellXf. |
64
|
|
|
* |
65
|
|
|
* @var int |
66
|
|
|
*/ |
67
|
|
|
private $xfIndex = 0; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* Attributes of the formula. |
71
|
|
|
*/ |
72
|
|
|
private $formulaAttributes; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Update the cell into the cell collection. |
76
|
|
|
* |
77
|
|
|
* @return self |
78
|
|
|
*/ |
79
|
121 |
|
public function updateInCollection() |
80
|
|
|
{ |
81
|
121 |
|
$this->parent->update($this); |
82
|
|
|
|
83
|
121 |
|
return $this; |
84
|
|
|
} |
85
|
|
|
|
86
|
111 |
|
public function detach() |
87
|
|
|
{ |
88
|
111 |
|
$this->parent = null; |
89
|
111 |
|
} |
90
|
|
|
|
91
|
103 |
|
public function attach(Cells $parent) |
92
|
|
|
{ |
93
|
103 |
|
$this->parent = $parent; |
94
|
103 |
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Create a new Cell. |
98
|
|
|
* |
99
|
|
|
* @param mixed $pValue |
100
|
|
|
* @param string $pDataType |
101
|
|
|
* @param Worksheet $pSheet |
102
|
|
|
* |
103
|
|
|
* @throws Exception |
104
|
|
|
*/ |
105
|
125 |
|
public function __construct($pValue, $pDataType, Worksheet $pSheet) |
106
|
|
|
{ |
107
|
|
|
// Initialise cell value |
108
|
125 |
|
$this->value = $pValue; |
109
|
|
|
|
110
|
|
|
// Set worksheet cache |
111
|
125 |
|
$this->parent = $pSheet->getCellCollection(); |
112
|
|
|
|
113
|
|
|
// Set datatype? |
114
|
125 |
|
if ($pDataType !== null) { |
115
|
125 |
|
if ($pDataType == DataType::TYPE_STRING2) { |
116
|
|
|
$pDataType = DataType::TYPE_STRING; |
117
|
|
|
} |
118
|
125 |
|
$this->dataType = $pDataType; |
119
|
|
|
} elseif (!self::getValueBinder()->bindValue($this, $pValue)) { |
120
|
|
|
throw new Exception('Value could not be bound to cell.'); |
121
|
|
|
} |
122
|
125 |
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Get cell coordinate column. |
126
|
|
|
* |
127
|
|
|
* @return string |
128
|
|
|
*/ |
129
|
48 |
|
public function getColumn() |
130
|
|
|
{ |
131
|
48 |
|
return $this->parent->getCurrentColumn(); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Get cell coordinate row. |
136
|
|
|
* |
137
|
|
|
* @return int |
138
|
|
|
*/ |
139
|
46 |
|
public function getRow() |
140
|
|
|
{ |
141
|
46 |
|
return $this->parent->getCurrentRow(); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Get cell coordinate. |
146
|
|
|
* |
147
|
|
|
* @return string |
148
|
|
|
*/ |
149
|
125 |
|
public function getCoordinate() |
150
|
|
|
{ |
151
|
125 |
|
return $this->parent->getCurrentCoordinate(); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Get cell value. |
156
|
|
|
* |
157
|
|
|
* @return mixed |
158
|
|
|
*/ |
159
|
109 |
|
public function getValue() |
160
|
|
|
{ |
161
|
109 |
|
return $this->value; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Get cell value with formatting. |
166
|
|
|
* |
167
|
|
|
* @return string |
168
|
|
|
*/ |
169
|
5 |
|
public function getFormattedValue() |
170
|
|
|
{ |
171
|
5 |
|
return (string) NumberFormat::toFormattedString( |
172
|
5 |
|
$this->getCalculatedValue(), |
173
|
5 |
|
$this->getStyle() |
174
|
5 |
|
->getNumberFormat()->getFormatCode() |
175
|
|
|
); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Set cell value. |
180
|
|
|
* |
181
|
|
|
* Sets the value for a cell, automatically determining the datatype using the value binder |
182
|
|
|
* |
183
|
|
|
* @param mixed $pValue Value |
184
|
|
|
* |
185
|
|
|
* @throws Exception |
186
|
|
|
* |
187
|
|
|
* @return Cell |
188
|
|
|
*/ |
189
|
79 |
|
public function setValue($pValue) |
190
|
|
|
{ |
191
|
79 |
|
if (!self::getValueBinder()->bindValue($this, $pValue)) { |
192
|
|
|
throw new Exception('Value could not be bound to cell.'); |
193
|
|
|
} |
194
|
|
|
|
195
|
79 |
|
return $this; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder). |
200
|
|
|
* |
201
|
|
|
* @param mixed $pValue Value |
202
|
|
|
* @param string $pDataType Explicit data type, see DataType::TYPE_* |
203
|
|
|
* |
204
|
|
|
* @throws Exception |
205
|
|
|
* |
206
|
|
|
* @return Cell |
207
|
|
|
*/ |
208
|
117 |
|
public function setValueExplicit($pValue, $pDataType) |
209
|
|
|
{ |
210
|
|
|
// set the value according to data type |
211
|
|
|
switch ($pDataType) { |
212
|
117 |
|
case DataType::TYPE_NULL: |
213
|
29 |
|
$this->value = $pValue; |
214
|
|
|
|
215
|
29 |
|
break; |
216
|
117 |
|
case DataType::TYPE_STRING2: |
217
|
|
|
$pDataType = DataType::TYPE_STRING; |
218
|
|
|
// no break |
219
|
117 |
|
case DataType::TYPE_STRING: |
220
|
|
|
// Synonym for string |
221
|
100 |
|
case DataType::TYPE_INLINE: |
222
|
|
|
// Rich text |
223
|
101 |
|
$this->value = DataType::checkString($pValue); |
224
|
|
|
|
225
|
101 |
|
break; |
226
|
99 |
|
case DataType::TYPE_NUMERIC: |
227
|
93 |
|
$this->value = (float) $pValue; |
228
|
|
|
|
229
|
93 |
|
break; |
230
|
59 |
|
case DataType::TYPE_FORMULA: |
231
|
57 |
|
$this->value = (string) $pValue; |
232
|
|
|
|
233
|
57 |
|
break; |
234
|
25 |
|
case DataType::TYPE_BOOL: |
235
|
25 |
|
$this->value = (bool) $pValue; |
236
|
|
|
|
237
|
25 |
|
break; |
238
|
|
|
case DataType::TYPE_ERROR: |
239
|
|
|
$this->value = DataType::checkErrorCode($pValue); |
240
|
|
|
|
241
|
|
|
break; |
242
|
|
|
default: |
243
|
|
|
throw new Exception('Invalid datatype: ' . $pDataType); |
244
|
|
|
break; |
|
|
|
|
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
// set the datatype |
248
|
117 |
|
$this->dataType = $pDataType; |
249
|
|
|
|
250
|
117 |
|
return $this->updateInCollection(); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Get calculated cell value. |
255
|
|
|
* |
256
|
|
|
* @param bool $resetLog Whether the calculation engine logger should be reset or not |
257
|
|
|
* |
258
|
|
|
* @throws Exception |
259
|
|
|
* |
260
|
|
|
* @return mixed |
261
|
|
|
*/ |
262
|
71 |
|
public function getCalculatedValue($resetLog = true) |
263
|
|
|
{ |
264
|
71 |
|
if ($this->dataType == DataType::TYPE_FORMULA) { |
265
|
|
|
try { |
266
|
48 |
|
$result = Calculation::getInstance( |
267
|
48 |
|
$this->getWorksheet()->getParent() |
268
|
48 |
|
)->calculateCellValue($this, $resetLog); |
269
|
|
|
// We don't yet handle array returns |
270
|
48 |
|
if (is_array($result)) { |
271
|
48 |
|
while (is_array($result)) { |
272
|
10 |
|
$result = array_pop($result); |
273
|
|
|
} |
274
|
|
|
} |
275
|
|
|
} catch (Exception $ex) { |
276
|
|
|
if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) { |
277
|
|
|
return $this->calculatedValue; // Fallback for calculations referencing external files. |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception( |
281
|
|
|
$this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage() |
282
|
|
|
); |
283
|
|
|
} |
284
|
|
|
|
285
|
48 |
|
if ($result === '#Not Yet Implemented') { |
286
|
|
|
return $this->calculatedValue; // Fallback if calculation engine does not support the formula. |
287
|
|
|
} |
288
|
|
|
|
289
|
48 |
|
return $result; |
290
|
71 |
|
} elseif ($this->value instanceof RichText) { |
291
|
2 |
|
return $this->value->getPlainText(); |
292
|
|
|
} |
293
|
|
|
|
294
|
71 |
|
return $this->value; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Set old calculated value (cached). |
299
|
|
|
* |
300
|
|
|
* @param mixed $pValue Value |
301
|
|
|
* |
302
|
|
|
* @return Cell |
303
|
|
|
*/ |
304
|
22 |
|
public function setCalculatedValue($pValue) |
305
|
|
|
{ |
306
|
22 |
|
if ($pValue !== null) { |
307
|
22 |
|
$this->calculatedValue = (is_numeric($pValue)) ? (float) $pValue : $pValue; |
308
|
|
|
} |
309
|
|
|
|
310
|
22 |
|
return $this->updateInCollection(); |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Get old calculated value (cached) |
315
|
|
|
* This returns the value last calculated by MS Excel or whichever spreadsheet program was used to |
316
|
|
|
* create the original spreadsheet file. |
317
|
|
|
* Note that this value is not guaranteed to refelect the actual calculated value because it is |
318
|
|
|
* possible that auto-calculation was disabled in the original spreadsheet, and underlying data |
319
|
|
|
* values used by the formula have changed since it was last calculated. |
320
|
|
|
* |
321
|
|
|
* @return mixed |
322
|
|
|
*/ |
323
|
|
|
public function getOldCalculatedValue() |
324
|
|
|
{ |
325
|
|
|
return $this->calculatedValue; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Get cell data type. |
330
|
|
|
* |
331
|
|
|
* @return string |
332
|
|
|
*/ |
333
|
70 |
|
public function getDataType() |
334
|
|
|
{ |
335
|
70 |
|
return $this->dataType; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Set cell data type. |
340
|
|
|
* |
341
|
|
|
* @param string $pDataType see DataType::TYPE_* |
342
|
|
|
* |
343
|
|
|
* @return Cell |
344
|
|
|
*/ |
345
|
|
|
public function setDataType($pDataType) |
346
|
|
|
{ |
347
|
|
|
if ($pDataType == DataType::TYPE_STRING2) { |
348
|
|
|
$pDataType = DataType::TYPE_STRING; |
349
|
|
|
} |
350
|
|
|
$this->dataType = $pDataType; |
351
|
|
|
|
352
|
|
|
return $this->updateInCollection(); |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Identify if the cell contains a formula. |
357
|
|
|
* |
358
|
|
|
* @return bool |
359
|
|
|
*/ |
360
|
|
|
public function isFormula() |
361
|
|
|
{ |
362
|
|
|
return $this->dataType == DataType::TYPE_FORMULA; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* Does this cell contain Data validation rules? |
367
|
|
|
* |
368
|
|
|
* @throws Exception |
369
|
|
|
* |
370
|
|
|
* @return bool |
371
|
|
|
*/ |
372
|
3 |
|
public function hasDataValidation() |
373
|
|
|
{ |
374
|
3 |
|
if (!isset($this->parent)) { |
375
|
|
|
throw new Exception('Cannot check for data validation when cell is not bound to a worksheet'); |
376
|
|
|
} |
377
|
|
|
|
378
|
3 |
|
return $this->getWorksheet()->dataValidationExists($this->getCoordinate()); |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* Get Data validation rules. |
383
|
|
|
* |
384
|
|
|
* @throws Exception |
385
|
|
|
* |
386
|
|
|
* @return DataValidation |
387
|
|
|
*/ |
388
|
4 |
|
public function getDataValidation() |
389
|
|
|
{ |
390
|
4 |
|
if (!isset($this->parent)) { |
391
|
|
|
throw new Exception('Cannot get data validation for cell that is not bound to a worksheet'); |
392
|
|
|
} |
393
|
|
|
|
394
|
4 |
|
return $this->getWorksheet()->getDataValidation($this->getCoordinate()); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Set Data validation rules. |
399
|
|
|
* |
400
|
|
|
* @param DataValidation $pDataValidation |
401
|
|
|
* |
402
|
|
|
* @throws Exception |
403
|
|
|
* |
404
|
|
|
* @return Cell |
405
|
|
|
*/ |
406
|
|
View Code Duplication |
public function setDataValidation(DataValidation $pDataValidation = null) |
|
|
|
|
407
|
|
|
{ |
408
|
|
|
if (!isset($this->parent)) { |
409
|
|
|
throw new Exception('Cannot set data validation for cell that is not bound to a worksheet'); |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
$this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation); |
413
|
|
|
|
414
|
|
|
return $this->updateInCollection(); |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Does this cell contain valid value? |
419
|
|
|
* |
420
|
|
|
* @return bool |
421
|
|
|
*/ |
422
|
3 |
|
public function hasValidValue() |
423
|
|
|
{ |
424
|
3 |
|
$validator = new DataValidator(); |
425
|
|
|
|
426
|
3 |
|
return $validator->isValid($this); |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/** |
430
|
|
|
* Does this cell contain a Hyperlink? |
431
|
|
|
* |
432
|
|
|
* @throws Exception |
433
|
|
|
* |
434
|
|
|
* @return bool |
435
|
|
|
*/ |
436
|
|
|
public function hasHyperlink() |
437
|
|
|
{ |
438
|
|
|
if (!isset($this->parent)) { |
439
|
|
|
throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet'); |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
return $this->getWorksheet()->hyperlinkExists($this->getCoordinate()); |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
/** |
446
|
|
|
* Get Hyperlink. |
447
|
|
|
* |
448
|
|
|
* @throws Exception |
449
|
|
|
* |
450
|
|
|
* @return Hyperlink |
451
|
|
|
*/ |
452
|
21 |
|
public function getHyperlink() |
453
|
|
|
{ |
454
|
21 |
|
if (!isset($this->parent)) { |
455
|
|
|
throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet'); |
456
|
|
|
} |
457
|
|
|
|
458
|
21 |
|
return $this->getWorksheet()->getHyperlink($this->getCoordinate()); |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* Set Hyperlink. |
463
|
|
|
* |
464
|
|
|
* @param Hyperlink $pHyperlink |
465
|
|
|
* |
466
|
|
|
* @throws Exception |
467
|
|
|
* |
468
|
|
|
* @return Cell |
469
|
|
|
*/ |
470
|
|
View Code Duplication |
public function setHyperlink(Hyperlink $pHyperlink = null) |
|
|
|
|
471
|
|
|
{ |
472
|
|
|
if (!isset($this->parent)) { |
473
|
|
|
throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet'); |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
$this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink); |
477
|
|
|
|
478
|
|
|
return $this->updateInCollection(); |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* Get cell collection. |
483
|
|
|
* |
484
|
|
|
* @return Cells |
485
|
|
|
*/ |
486
|
64 |
|
public function getParent() |
487
|
|
|
{ |
488
|
64 |
|
return $this->parent; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* Get parent worksheet. |
493
|
|
|
* |
494
|
|
|
* @return Worksheet |
495
|
|
|
*/ |
496
|
80 |
|
public function getWorksheet() |
497
|
|
|
{ |
498
|
80 |
|
return $this->parent->getParent(); |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
/** |
502
|
|
|
* Is this cell in a merge range. |
503
|
|
|
* |
504
|
|
|
* @return bool |
505
|
|
|
*/ |
506
|
|
|
public function isInMergeRange() |
507
|
|
|
{ |
508
|
|
|
return (bool) $this->getMergeRange(); |
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
/** |
512
|
|
|
* Is this cell the master (top left cell) in a merge range (that holds the actual data value). |
513
|
|
|
* |
514
|
|
|
* @return bool |
515
|
|
|
*/ |
516
|
|
|
public function isMergeRangeValueCell() |
517
|
|
|
{ |
518
|
|
|
if ($mergeRange = $this->getMergeRange()) { |
519
|
|
|
$mergeRange = self::splitRange($mergeRange); |
|
|
|
|
520
|
|
|
list($startCell) = $mergeRange[0]; |
521
|
|
|
if ($this->getCoordinate() === $startCell) { |
522
|
|
|
return true; |
523
|
|
|
} |
524
|
|
|
} |
525
|
|
|
|
526
|
|
|
return false; |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
/** |
530
|
|
|
* If this cell is in a merge range, then return the range. |
531
|
|
|
* |
532
|
|
|
* @return string |
533
|
|
|
*/ |
534
|
|
|
public function getMergeRange() |
535
|
|
|
{ |
536
|
|
|
foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) { |
537
|
|
|
if ($this->isInRange($mergeRange)) { |
|
|
|
|
538
|
|
|
return $mergeRange; |
539
|
|
|
} |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
return false; |
543
|
|
|
} |
544
|
|
|
|
545
|
|
|
/** |
546
|
|
|
* Get cell style. |
547
|
|
|
* |
548
|
|
|
* @return Style |
549
|
|
|
*/ |
550
|
6 |
|
public function getStyle() |
551
|
|
|
{ |
552
|
6 |
|
return $this->getWorksheet()->getStyle($this->getCoordinate()); |
553
|
|
|
} |
554
|
|
|
|
555
|
|
|
/** |
556
|
|
|
* Re-bind parent. |
557
|
|
|
* |
558
|
|
|
* @param Worksheet $parent |
559
|
|
|
* |
560
|
|
|
* @return Cell |
561
|
|
|
*/ |
562
|
|
|
public function rebindParent(Worksheet $parent) |
563
|
|
|
{ |
564
|
|
|
$this->parent = $parent->getCellCollection(); |
565
|
|
|
|
566
|
|
|
return $this->updateInCollection(); |
567
|
|
|
} |
568
|
|
|
|
569
|
|
|
/** |
570
|
|
|
* Is cell in a specific range? |
571
|
|
|
* |
572
|
|
|
* @param string $pRange Cell range (e.g. A1:A1) |
573
|
|
|
* |
574
|
|
|
* @return bool |
575
|
|
|
*/ |
576
|
|
|
public function isInRange($pRange) |
577
|
|
|
{ |
578
|
|
|
list($rangeStart, $rangeEnd) = self::rangeBoundaries($pRange); |
579
|
|
|
|
580
|
|
|
// Translate properties |
581
|
|
|
$myColumn = self::columnIndexFromString($this->getColumn()); |
582
|
|
|
$myRow = $this->getRow(); |
583
|
|
|
|
584
|
|
|
// Verify if cell is in range |
585
|
|
|
return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) && |
586
|
|
|
($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow); |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
/** |
590
|
|
|
* Coordinate from string. |
591
|
|
|
* |
592
|
|
|
* @param string $pCoordinateString eg: 'A1' |
593
|
|
|
* |
594
|
|
|
* @throws Exception |
595
|
|
|
* |
596
|
|
|
* @return string[] Array containing column and row (indexes 0 and 1) |
597
|
|
|
*/ |
598
|
174 |
|
public static function coordinateFromString($pCoordinateString) |
599
|
|
|
{ |
600
|
174 |
|
if (preg_match("/^([$]?[A-Z]{1,3})([$]?\d{1,7})$/", $pCoordinateString, $matches)) { |
601
|
171 |
|
return [$matches[1], $matches[2]]; |
602
|
4 |
|
} elseif ((strpos($pCoordinateString, ':') !== false) || (strpos($pCoordinateString, ',') !== false)) { |
603
|
1 |
|
throw new Exception('Cell coordinate string can not be a range of cells'); |
604
|
3 |
|
} elseif ($pCoordinateString == '') { |
605
|
1 |
|
throw new Exception('Cell coordinate can not be zero-length string'); |
606
|
|
|
} |
607
|
|
|
|
608
|
2 |
|
throw new Exception('Invalid cell coordinate ' . $pCoordinateString); |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
/** |
612
|
|
|
* Make string row, column or cell coordinate absolute. |
613
|
|
|
* |
614
|
|
|
* @param string $pCoordinateString e.g. 'A' or '1' or 'A1' |
615
|
|
|
* Note that this value can be a row or column reference as well as a cell reference |
616
|
|
|
* |
617
|
|
|
* @throws Exception |
618
|
|
|
* |
619
|
|
|
* @return string Absolute coordinate e.g. '$A' or '$1' or '$A$1' |
620
|
|
|
*/ |
621
|
21 |
|
public static function absoluteReference($pCoordinateString) |
622
|
|
|
{ |
623
|
21 |
|
if (strpos($pCoordinateString, ':') === false && strpos($pCoordinateString, ',') === false) { |
624
|
|
|
// Split out any worksheet name from the reference |
625
|
20 |
|
$worksheet = ''; |
626
|
20 |
|
$cellAddress = explode('!', $pCoordinateString); |
627
|
20 |
|
if (count($cellAddress) > 1) { |
628
|
8 |
|
list($worksheet, $pCoordinateString) = $cellAddress; |
629
|
|
|
} |
630
|
20 |
|
if ($worksheet > '') { |
631
|
8 |
|
$worksheet .= '!'; |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
// Create absolute coordinate |
635
|
20 |
|
if (ctype_digit($pCoordinateString)) { |
636
|
2 |
|
return $worksheet . '$' . $pCoordinateString; |
637
|
18 |
|
} elseif (ctype_alpha($pCoordinateString)) { |
638
|
2 |
|
return $worksheet . '$' . strtoupper($pCoordinateString); |
639
|
|
|
} |
640
|
|
|
|
641
|
16 |
|
return $worksheet . self::absoluteCoordinate($pCoordinateString); |
642
|
|
|
} |
643
|
|
|
|
644
|
1 |
|
throw new Exception('Cell coordinate string can not be a range of cells'); |
645
|
|
|
} |
646
|
|
|
|
647
|
|
|
/** |
648
|
|
|
* Make string coordinate absolute. |
649
|
|
|
* |
650
|
|
|
* @param string $pCoordinateString e.g. 'A1' |
651
|
|
|
* |
652
|
|
|
* @throws Exception |
653
|
|
|
* |
654
|
|
|
* @return string Absolute coordinate e.g. '$A$1' |
655
|
|
|
*/ |
656
|
32 |
|
public static function absoluteCoordinate($pCoordinateString) |
657
|
|
|
{ |
658
|
32 |
|
if (strpos($pCoordinateString, ':') === false && strpos($pCoordinateString, ',') === false) { |
659
|
|
|
// Split out any worksheet name from the coordinate |
660
|
31 |
|
$worksheet = ''; |
661
|
31 |
|
$cellAddress = explode('!', $pCoordinateString); |
662
|
31 |
|
if (count($cellAddress) > 1) { |
663
|
6 |
|
list($worksheet, $pCoordinateString) = $cellAddress; |
664
|
|
|
} |
665
|
31 |
|
if ($worksheet > '') { |
666
|
6 |
|
$worksheet .= '!'; |
667
|
|
|
} |
668
|
|
|
|
669
|
|
|
// Create absolute coordinate |
670
|
31 |
|
list($column, $row) = self::coordinateFromString($pCoordinateString); |
671
|
31 |
|
$column = ltrim($column, '$'); |
672
|
31 |
|
$row = ltrim($row, '$'); |
673
|
|
|
|
674
|
31 |
|
return $worksheet . '$' . $column . '$' . $row; |
675
|
|
|
} |
676
|
|
|
|
677
|
1 |
|
throw new Exception('Cell coordinate string can not be a range of cells'); |
678
|
|
|
} |
679
|
|
|
|
680
|
|
|
/** |
681
|
|
|
* Split range into coordinate strings. |
682
|
|
|
* |
683
|
|
|
* @param string $pRange e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4' |
684
|
|
|
* |
685
|
|
|
* @return array Array containg one or more arrays containing one or two coordinate strings |
686
|
|
|
* e.g. array('B4','D9') or array(array('B4','D9'),array('H2','O11')) |
687
|
|
|
* or array('B4') |
688
|
|
|
*/ |
689
|
104 |
|
public static function splitRange($pRange) |
690
|
|
|
{ |
691
|
|
|
// Ensure $pRange is a valid range |
692
|
104 |
|
if (empty($pRange)) { |
693
|
|
|
$pRange = self::DEFAULT_RANGE; |
694
|
|
|
} |
695
|
|
|
|
696
|
104 |
|
$exploded = explode(',', $pRange); |
697
|
104 |
|
$counter = count($exploded); |
698
|
104 |
|
for ($i = 0; $i < $counter; ++$i) { |
699
|
104 |
|
$exploded[$i] = explode(':', $exploded[$i]); |
700
|
|
|
} |
701
|
|
|
|
702
|
104 |
|
return $exploded; |
703
|
|
|
} |
704
|
|
|
|
705
|
|
|
/** |
706
|
|
|
* Build range from coordinate strings. |
707
|
|
|
* |
708
|
|
|
* @param array $pRange Array containg one or more arrays containing one or two coordinate strings |
709
|
|
|
* |
710
|
|
|
* @throws Exception |
711
|
|
|
* |
712
|
|
|
* @return string String representation of $pRange |
713
|
|
|
*/ |
714
|
19 |
|
public static function buildRange(array $pRange) |
715
|
|
|
{ |
716
|
|
|
// Verify range |
717
|
19 |
|
if (empty($pRange) || !is_array($pRange[0])) { |
718
|
|
|
throw new Exception('Range does not contain any information'); |
719
|
|
|
} |
720
|
|
|
|
721
|
|
|
// Build range |
722
|
19 |
|
$imploded = []; |
|
|
|
|
723
|
19 |
|
$counter = count($pRange); |
724
|
19 |
|
for ($i = 0; $i < $counter; ++$i) { |
725
|
19 |
|
$pRange[$i] = implode(':', $pRange[$i]); |
726
|
|
|
} |
727
|
19 |
|
$imploded = implode(',', $pRange); |
728
|
|
|
|
729
|
19 |
|
return $imploded; |
730
|
|
|
} |
731
|
|
|
|
732
|
|
|
/** |
733
|
|
|
* Calculate range boundaries. |
734
|
|
|
* |
735
|
|
|
* @param string $pRange Cell range (e.g. A1:A1) |
736
|
|
|
* |
737
|
|
|
* @return array Range coordinates array(Start Cell, End Cell) |
738
|
|
|
* where Start Cell and End Cell are arrays (Column Number, Row Number) |
739
|
|
|
*/ |
740
|
65 |
|
public static function rangeBoundaries($pRange) |
741
|
|
|
{ |
742
|
|
|
// Ensure $pRange is a valid range |
743
|
65 |
|
if (empty($pRange)) { |
744
|
|
|
$pRange = self::DEFAULT_RANGE; |
745
|
|
|
} |
746
|
|
|
|
747
|
|
|
// Uppercase coordinate |
748
|
65 |
|
$pRange = strtoupper($pRange); |
749
|
|
|
|
750
|
|
|
// Extract range |
751
|
65 |
View Code Duplication |
if (strpos($pRange, ':') === false) { |
|
|
|
|
752
|
2 |
|
$rangeA = $rangeB = $pRange; |
753
|
|
|
} else { |
754
|
63 |
|
list($rangeA, $rangeB) = explode(':', $pRange); |
755
|
|
|
} |
756
|
|
|
|
757
|
|
|
// Calculate range outer borders |
758
|
65 |
|
$rangeStart = self::coordinateFromString($rangeA); |
759
|
65 |
|
$rangeEnd = self::coordinateFromString($rangeB); |
760
|
|
|
|
761
|
|
|
// Translate column into index |
762
|
65 |
|
$rangeStart[0] = self::columnIndexFromString($rangeStart[0]); |
763
|
65 |
|
$rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]); |
764
|
|
|
|
765
|
65 |
|
return [$rangeStart, $rangeEnd]; |
766
|
|
|
} |
767
|
|
|
|
768
|
|
|
/** |
769
|
|
|
* Calculate range dimension. |
770
|
|
|
* |
771
|
|
|
* @param string $pRange Cell range (e.g. A1:A1) |
772
|
|
|
* |
773
|
|
|
* @return array Range dimension (width, height) |
774
|
|
|
*/ |
775
|
16 |
|
public static function rangeDimension($pRange) |
776
|
|
|
{ |
777
|
|
|
// Calculate range outer borders |
778
|
16 |
|
list($rangeStart, $rangeEnd) = self::rangeBoundaries($pRange); |
779
|
|
|
|
780
|
16 |
|
return [($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1)]; |
781
|
|
|
} |
782
|
|
|
|
783
|
|
|
/** |
784
|
|
|
* Calculate range boundaries. |
785
|
|
|
* |
786
|
|
|
* @param string $pRange Cell range (e.g. A1:A1) |
787
|
|
|
* |
788
|
|
|
* @return array Range coordinates array(Start Cell, End Cell) |
789
|
|
|
* where Start Cell and End Cell are arrays (Column ID, Row Number) |
790
|
|
|
*/ |
791
|
13 |
|
public static function getRangeBoundaries($pRange) |
792
|
|
|
{ |
793
|
|
|
// Ensure $pRange is a valid range |
794
|
13 |
|
if (empty($pRange)) { |
795
|
|
|
$pRange = self::DEFAULT_RANGE; |
796
|
|
|
} |
797
|
|
|
|
798
|
|
|
// Uppercase coordinate |
799
|
13 |
|
$pRange = strtoupper($pRange); |
800
|
|
|
|
801
|
|
|
// Extract range |
802
|
13 |
View Code Duplication |
if (strpos($pRange, ':') === false) { |
|
|
|
|
803
|
1 |
|
$rangeA = $rangeB = $pRange; |
804
|
|
|
} else { |
805
|
12 |
|
list($rangeA, $rangeB) = explode(':', $pRange); |
806
|
|
|
} |
807
|
|
|
|
808
|
13 |
|
return [self::coordinateFromString($rangeA), self::coordinateFromString($rangeB)]; |
809
|
|
|
} |
810
|
|
|
|
811
|
|
|
/** |
812
|
|
|
* Column index from string. |
813
|
|
|
* |
814
|
|
|
* @param string $pString eg 'A' |
815
|
|
|
* |
816
|
|
|
* @return int Column index (base 1 !!!) |
817
|
|
|
*/ |
818
|
166 |
|
public static function columnIndexFromString($pString) |
819
|
|
|
{ |
820
|
|
|
// Using a lookup cache adds a slight memory overhead, but boosts speed |
821
|
|
|
// caching using a static within the method is faster than a class static, |
822
|
|
|
// though it's additional memory overhead |
823
|
166 |
|
static $indexCache = []; |
824
|
|
|
|
825
|
166 |
|
if (isset($indexCache[$pString])) { |
826
|
151 |
|
return $indexCache[$pString]; |
827
|
|
|
} |
828
|
|
|
// It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array rather than use ord() |
829
|
|
|
// and make it case insensitive to get rid of the strtoupper() as well. Because it's a static, there's no significant |
830
|
|
|
// memory overhead either |
831
|
118 |
|
static $columnLookup = [ |
832
|
|
|
'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, |
833
|
|
|
'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, |
834
|
|
|
'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, |
835
|
|
|
'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, |
836
|
|
|
]; |
837
|
|
|
|
838
|
|
|
// We also use the language construct isset() rather than the more costly strlen() function to match the length of $pString |
839
|
|
|
// for improved performance |
840
|
118 |
|
if (isset($pString[0])) { |
841
|
116 |
|
if (!isset($pString[1])) { |
842
|
105 |
|
$indexCache[$pString] = $columnLookup[$pString]; |
843
|
|
|
|
844
|
105 |
|
return $indexCache[$pString]; |
845
|
12 |
|
} elseif (!isset($pString[2])) { |
846
|
9 |
|
$indexCache[$pString] = $columnLookup[$pString[0]] * 26 + $columnLookup[$pString[1]]; |
847
|
|
|
|
848
|
9 |
|
return $indexCache[$pString]; |
849
|
3 |
|
} elseif (!isset($pString[3])) { |
850
|
2 |
|
$indexCache[$pString] = $columnLookup[$pString[0]] * 676 + $columnLookup[$pString[1]] * 26 + $columnLookup[$pString[2]]; |
851
|
|
|
|
852
|
2 |
|
return $indexCache[$pString]; |
853
|
|
|
} |
854
|
|
|
} |
855
|
|
|
|
856
|
3 |
|
throw new Exception('Column string index can not be ' . ((isset($pString[0])) ? 'longer than 3 characters' : 'empty')); |
857
|
|
|
} |
858
|
|
|
|
859
|
|
|
/** |
860
|
|
|
* String from columnindex. |
861
|
|
|
* |
862
|
|
|
* @param int $columnIndex Column index (A = 0) |
863
|
|
|
* |
864
|
|
|
* @return string |
865
|
|
|
*/ |
866
|
133 |
|
public static function stringFromColumnIndex($columnIndex) |
867
|
|
|
{ |
868
|
133 |
|
static $indexCache = []; |
869
|
|
|
|
870
|
133 |
|
if (!isset($indexCache[$columnIndex])) { |
871
|
114 |
|
$indexValue = $columnIndex + 1; |
872
|
114 |
|
$base26 = null; |
873
|
|
|
do { |
874
|
114 |
|
$characterValue = ($indexValue % 26) ?: 26; |
875
|
114 |
|
$indexValue = ($indexValue - $characterValue) / 26; |
876
|
114 |
|
$base26 = chr($characterValue + 64) . ($base26 ?: ''); |
877
|
114 |
|
} while ($indexValue > 0); |
878
|
114 |
|
$indexCache[$columnIndex] = $base26; |
879
|
|
|
} |
880
|
|
|
|
881
|
133 |
|
return $indexCache[$columnIndex]; |
882
|
|
|
} |
883
|
|
|
|
884
|
|
|
/** |
885
|
|
|
* Extract all cell references in range. |
886
|
|
|
* |
887
|
|
|
* @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25) |
888
|
|
|
* |
889
|
|
|
* @return array Array containing single cell references |
890
|
|
|
*/ |
891
|
76 |
|
public static function extractAllCellReferencesInRange($pRange) |
892
|
|
|
{ |
893
|
|
|
// Returnvalue |
894
|
76 |
|
$returnValue = []; |
895
|
|
|
|
896
|
|
|
// Explode spaces |
897
|
76 |
|
$cellBlocks = explode(' ', str_replace('$', '', strtoupper($pRange))); |
898
|
76 |
|
foreach ($cellBlocks as $cellBlock) { |
899
|
|
|
// Single cell? |
900
|
76 |
|
if (strpos($cellBlock, ':') === false && strpos($cellBlock, ',') === false) { |
901
|
61 |
|
$returnValue[] = $cellBlock; |
902
|
|
|
|
903
|
61 |
|
continue; |
904
|
|
|
} |
905
|
|
|
|
906
|
|
|
// Range... |
907
|
68 |
|
$ranges = self::splitRange($cellBlock); |
908
|
68 |
|
foreach ($ranges as $range) { |
909
|
|
|
// Single cell? |
910
|
68 |
|
if (!isset($range[1])) { |
911
|
|
|
$returnValue[] = $range[0]; |
912
|
|
|
|
913
|
|
|
continue; |
914
|
|
|
} |
915
|
|
|
|
916
|
|
|
// Range... |
917
|
68 |
|
list($rangeStart, $rangeEnd) = $range; |
918
|
68 |
|
sscanf($rangeStart, '%[A-Z]%d', $startCol, $startRow); |
|
|
|
|
919
|
68 |
|
sscanf($rangeEnd, '%[A-Z]%d', $endCol, $endRow); |
|
|
|
|
920
|
68 |
|
++$endCol; |
921
|
|
|
|
922
|
|
|
// Current data |
923
|
68 |
|
$currentCol = $startCol; |
924
|
68 |
|
$currentRow = $startRow; |
925
|
|
|
|
926
|
|
|
// Loop cells |
927
|
68 |
|
while ($currentCol != $endCol) { |
928
|
68 |
|
while ($currentRow <= $endRow) { |
929
|
68 |
|
$returnValue[] = $currentCol . $currentRow; |
930
|
68 |
|
++$currentRow; |
931
|
|
|
} |
932
|
68 |
|
++$currentCol; |
933
|
68 |
|
$currentRow = $startRow; |
934
|
|
|
} |
935
|
|
|
} |
936
|
|
|
} |
937
|
|
|
|
938
|
|
|
// Sort the result by column and row |
939
|
76 |
|
$sortKeys = []; |
940
|
76 |
|
foreach (array_unique($returnValue) as $coord) { |
941
|
76 |
|
sscanf($coord, '%[A-Z]%d', $column, $row); |
|
|
|
|
942
|
76 |
|
$sortKeys[sprintf('%3s%09d', $column, $row)] = $coord; |
943
|
|
|
} |
944
|
76 |
|
ksort($sortKeys); |
945
|
|
|
|
946
|
|
|
// Return value |
947
|
76 |
|
return array_values($sortKeys); |
948
|
|
|
} |
949
|
|
|
|
950
|
|
|
/** |
951
|
|
|
* Convert an associative array of single cell coordinates to values to an associative array |
952
|
|
|
* of cell ranges to values. Only adjacent cell coordinates with the same |
953
|
|
|
* value will be merged. If the value is an object, it must implement the method getHashCode(). |
954
|
|
|
* |
955
|
|
|
* For example, this function converts: |
956
|
|
|
* |
957
|
|
|
* [ 'A1' => 'x', 'A2' => 'x', 'A3' => 'x', 'A4' => 'y' ] |
958
|
|
|
* |
959
|
|
|
* to: |
960
|
|
|
* |
961
|
|
|
* [ 'A1:A3' => 'x', 'A4' => 'y' ] |
962
|
|
|
* |
963
|
|
|
* @param array $pCoordCollection associative array mapping coordinates to values |
964
|
|
|
* |
965
|
|
|
* @return array associative array mapping coordinate ranges to valuea |
966
|
|
|
*/ |
967
|
6 |
|
public static function mergeRangesInCollection(array $pCoordCollection) |
968
|
|
|
{ |
969
|
6 |
|
$hashedValues = []; |
970
|
|
|
|
971
|
6 |
|
foreach ($pCoordCollection as $coord => $value) { |
972
|
6 |
|
list($column, $row) = self::coordinateFromString($coord); |
973
|
6 |
|
$row = (int) (ltrim($row, '$')); |
974
|
6 |
|
$hashCode = $column . '-' . (is_object($value) ? $value->getHashCode() : $value); |
975
|
|
|
|
976
|
6 |
|
if (!isset($hashedValues[$hashCode])) { |
977
|
6 |
|
$hashedValues[$hashCode] = (object) [ |
978
|
6 |
|
'value' => $value, |
979
|
6 |
|
'col' => $column, |
980
|
6 |
|
'rows' => [$row], |
981
|
|
|
]; |
982
|
|
|
} else { |
983
|
6 |
|
$hashedValues[$hashCode]->rows[] = $row; |
984
|
|
|
} |
985
|
|
|
} |
986
|
|
|
|
987
|
6 |
|
$mergedCoordCollection = []; |
988
|
6 |
|
ksort($hashedValues); |
989
|
|
|
|
990
|
6 |
|
foreach ($hashedValues as $hashedValue) { |
991
|
6 |
|
sort($hashedValue->rows); |
992
|
6 |
|
$rowStart = null; |
993
|
6 |
|
$rowEnd = null; |
994
|
6 |
|
$ranges = []; |
995
|
|
|
|
996
|
6 |
|
foreach ($hashedValue->rows as $row) { |
997
|
6 |
|
if ($rowStart === null) { |
998
|
6 |
|
$rowStart = $row; |
999
|
6 |
|
$rowEnd = $row; |
1000
|
3 |
|
} elseif ($rowEnd === $row - 1) { |
1001
|
3 |
|
$rowEnd = $row; |
1002
|
|
|
} else { |
1003
|
1 |
View Code Duplication |
if ($rowStart == $rowEnd) { |
|
|
|
|
1004
|
|
|
$ranges[] = $hashedValue->col . $rowStart; |
1005
|
|
|
} else { |
1006
|
1 |
|
$ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd; |
1007
|
|
|
} |
1008
|
|
|
|
1009
|
1 |
|
$rowStart = $row; |
1010
|
6 |
|
$rowEnd = $row; |
1011
|
|
|
} |
1012
|
|
|
} |
1013
|
|
|
|
1014
|
6 |
View Code Duplication |
if ($rowStart !== null) { |
|
|
|
|
1015
|
6 |
|
if ($rowStart == $rowEnd) { |
1016
|
5 |
|
$ranges[] = $hashedValue->col . $rowStart; |
1017
|
|
|
} else { |
1018
|
2 |
|
$ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd; |
1019
|
|
|
} |
1020
|
|
|
} |
1021
|
|
|
|
1022
|
6 |
|
foreach ($ranges as $range) { |
1023
|
6 |
|
$mergedCoordCollection[$range] = $hashedValue->value; |
1024
|
|
|
} |
1025
|
|
|
} |
1026
|
|
|
|
1027
|
6 |
|
return $mergedCoordCollection; |
1028
|
|
|
} |
1029
|
|
|
|
1030
|
|
|
/** |
1031
|
|
|
* Compare 2 cells. |
1032
|
|
|
* |
1033
|
|
|
* @param Cell $a Cell a |
1034
|
|
|
* @param Cell $b Cell b |
1035
|
|
|
* |
1036
|
|
|
* @return int Result of comparison (always -1 or 1, never zero!) |
1037
|
|
|
*/ |
1038
|
|
|
public static function compareCells(Cell $a, Cell $b) |
1039
|
|
|
{ |
1040
|
|
|
if ($a->getRow() < $b->getRow()) { |
1041
|
|
|
return -1; |
1042
|
|
|
} elseif ($a->getRow() > $b->getRow()) { |
1043
|
|
|
return 1; |
1044
|
|
|
} elseif (self::columnIndexFromString($a->getColumn()) < self::columnIndexFromString($b->getColumn())) { |
1045
|
|
|
return -1; |
1046
|
|
|
} |
1047
|
|
|
|
1048
|
|
|
return 1; |
1049
|
|
|
} |
1050
|
|
|
|
1051
|
|
|
/** |
1052
|
|
|
* Get value binder to use. |
1053
|
|
|
* |
1054
|
|
|
* @return IValueBinder |
1055
|
|
|
*/ |
1056
|
79 |
|
public static function getValueBinder() |
1057
|
|
|
{ |
1058
|
79 |
|
if (self::$valueBinder === null) { |
1059
|
71 |
|
self::$valueBinder = new DefaultValueBinder(); |
1060
|
|
|
} |
1061
|
|
|
|
1062
|
79 |
|
return self::$valueBinder; |
1063
|
|
|
} |
1064
|
|
|
|
1065
|
|
|
/** |
1066
|
|
|
* Set value binder to use. |
1067
|
|
|
* |
1068
|
|
|
* @param IValueBinder $binder |
1069
|
|
|
* |
1070
|
|
|
* @throws Exception |
1071
|
|
|
*/ |
1072
|
2 |
|
public static function setValueBinder(IValueBinder $binder) |
1073
|
|
|
{ |
1074
|
2 |
|
self::$valueBinder = $binder; |
1075
|
2 |
|
} |
1076
|
|
|
|
1077
|
|
|
/** |
1078
|
|
|
* Implement PHP __clone to create a deep clone, not just a shallow copy. |
1079
|
|
|
*/ |
1080
|
2 |
View Code Duplication |
public function __clone() |
|
|
|
|
1081
|
|
|
{ |
1082
|
2 |
|
$vars = get_object_vars($this); |
1083
|
2 |
|
foreach ($vars as $key => $value) { |
1084
|
2 |
|
if ((is_object($value)) && ($key != 'parent')) { |
1085
|
|
|
$this->$key = clone $value; |
1086
|
|
|
} else { |
1087
|
2 |
|
$this->$key = $value; |
1088
|
|
|
} |
1089
|
|
|
} |
1090
|
2 |
|
} |
1091
|
|
|
|
1092
|
|
|
/** |
1093
|
|
|
* Get index to cellXf. |
1094
|
|
|
* |
1095
|
|
|
* @return int |
1096
|
|
|
*/ |
1097
|
99 |
|
public function getXfIndex() |
1098
|
|
|
{ |
1099
|
99 |
|
return $this->xfIndex; |
1100
|
|
|
} |
1101
|
|
|
|
1102
|
|
|
/** |
1103
|
|
|
* Set index to cellXf. |
1104
|
|
|
* |
1105
|
|
|
* @param int $pValue |
1106
|
|
|
* |
1107
|
|
|
* @return Cell |
1108
|
|
|
*/ |
1109
|
92 |
|
public function setXfIndex($pValue) |
1110
|
|
|
{ |
1111
|
92 |
|
$this->xfIndex = $pValue; |
1112
|
|
|
|
1113
|
92 |
|
return $this->updateInCollection(); |
1114
|
|
|
} |
1115
|
|
|
|
1116
|
|
|
/** |
1117
|
|
|
* Set the formula attributes. |
1118
|
|
|
* |
1119
|
|
|
* @param mixed $pAttributes |
1120
|
|
|
*/ |
1121
|
|
|
public function setFormulaAttributes($pAttributes) |
1122
|
|
|
{ |
1123
|
|
|
$this->formulaAttributes = $pAttributes; |
1124
|
|
|
|
1125
|
|
|
return $this; |
1126
|
|
|
} |
1127
|
|
|
|
1128
|
|
|
/** |
1129
|
|
|
* Get the formula attributes. |
1130
|
|
|
*/ |
1131
|
18 |
|
public function getFormulaAttributes() |
1132
|
|
|
{ |
1133
|
18 |
|
return $this->formulaAttributes; |
1134
|
|
|
} |
1135
|
|
|
|
1136
|
|
|
/** |
1137
|
|
|
* Convert to string. |
1138
|
|
|
* |
1139
|
|
|
* @return string |
1140
|
|
|
*/ |
1141
|
|
|
public function __toString() |
1142
|
|
|
{ |
1143
|
|
|
return (string) $this->getValue(); |
1144
|
|
|
} |
1145
|
|
|
} |
1146
|
|
|
|
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
orexit
statements that have been added for debug purposes.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.