Skip to content

Commit 8d76020

Browse files
committed
Consistent stringFromColumnIndex() and columnIndexFromString()
Column indexes are always based on 1 everywhere in PhpSpreadsheet. This is consistent with rows starting at 1, as well as Excel function `COLUMN()`. It should also make it easier to reason about columns and rows and remove any doubts whether a specific method is expecting 0 based or 1 based indexes. Fixes #273 Fixes PHPOffice/PHPExcel#307 Fixes PHPOffice/PHPExcel#476
1 parent e0150fd commit 8d76020

32 files changed

+425
-352
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2929

3030
### BREAKING CHANGE
3131

32+
- Extracted coordinate method to dedicate class [migration guide](./docs/topics/migration-from-PHPExcel.md).
33+
- Column indexes are based on 1, see the [migration guide](./docs/topics/migration-from-PHPExcel.md).
3234
- Standardization of array keys used for style, see the [migration guide](./docs/topics/migration-from-PHPExcel.md).
3335
- Easier usage of PDF writers, and other custom readers and writers, see the [migration guide](./docs/topics/migration-from-PHPExcel.md).
3436
- Easier usage of chart renderers, see the [migration guide](./docs/topics/migration-from-PHPExcel.md).

docs/topics/accessing-cells.md

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,7 @@ the cell's `getFormattedValue()` method.
261261

262262
``` php
263263
// Get the value fom cell A6
264-
$cellValue = $spreadsheet->getActiveSheet()->getCell('A6')
265-
->getFormattedValue();
264+
$cellValue = $spreadsheet->getActiveSheet()->getCell('A6')->getFormattedValue();
266265
```
267266

268267
## Setting a cell value by column and row
@@ -281,22 +280,20 @@ than from `1`.
281280
## Retrieving a cell value by column and row
282281

283282
To retrieve the value of a cell, the cell should first be retrieved from
284-
the worksheet using the getCellByColumnAndRow method. A cell’s value can
283+
the worksheet using the `getCellByColumnAndRow()` method. A cell’s value can
285284
be read again using the following line of code:
286285

287286
``` php
288287
// Get the value fom cell B5
289-
$cellValue = $spreadsheet->getActiveSheet()->getCellByColumnAndRow(1, 5)
290-
->getValue();
288+
$cellValue = $spreadsheet->getActiveSheet()->getCellByColumnAndRow(2, 5)->getValue();
291289
```
292290

293291
If you need the calculated value of a cell, use the following code. This
294292
is further explained in .
295293

296294
``` php
297295
// Get the value fom cell A4
298-
$cellValue = $spreadsheet->getActiveSheet()->getCellByColumnAndRow(0, 4)
299-
->getCalculatedValue();
296+
$cellValue = $spreadsheet->getActiveSheet()->getCellByColumnAndRow(1, 4)->getCalculatedValue();
300297
```
301298

302299
## Retrieving a range of cell values to an array
@@ -374,8 +371,7 @@ One can use the possibility to access cell values by column and row
374371
index like (0,1) instead of 'A1' for reading and writing cell values in
375372
loops.
376373

377-
Note: In PhpSpreadsheet column index is 0-based while row index is
378-
1-based. That means 'A1' \~ (0,1)
374+
Note: In PhpSpreadsheet column index and row index are 1-based. That means `'A1'` ~ `[1, 1]`
379375

380376
Below is an example where we read all the values in a worksheet and
381377
display them in a table.
@@ -394,11 +390,9 @@ $highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell\Cell::columnIndexFromString
394390
echo '<table>' . "\n";
395391
for ($row = 1; $row <= $highestRow; ++$row) {
396392
echo '<tr>' . PHP_EOL;
397-
for ($col = 0; $col <= $highestColumnIndex; ++$col) {
398-
echo '<td>' .
399-
$worksheet->getCellByColumnAndRow($col, $row)
400-
->getValue() .
401-
'</td>' . PHP_EOL;
393+
for ($col = 1; $col <= $highestColumnIndex; ++$col) {
394+
$value = $worksheet->getCellByColumnAndRow($col, $row)->getValue();
395+
echo '<td>' . $value . '</td>' . PHP_EOL;
402396
}
403397
echo '</tr>' . PHP_EOL;
404398
}

docs/topics/migration-from-PHPExcel.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,3 +363,66 @@ $style = [
363363
],
364364
];
365365
```
366+
367+
### Dedicated class to manipulate coordinates
368+
369+
Methods to manipulate coordinates that used to exists in `PHPExcel_Cell` were extracted
370+
to a dedicated new class `\PhpOffice\PhpSpreadsheet\Cell\Coordinate`. The methods are:
371+
372+
- `absoluteCoordinate()`
373+
- `absoluteReference()`
374+
- `buildRange()`
375+
- `columnIndexFromString()`
376+
- `coordinateFromString()`
377+
- `extractAllCellReferencesInRange()`
378+
- `getRangeBoundaries()`
379+
- `mergeRangesInCollection()`
380+
- `rangeBoundaries()`
381+
- `rangeDimension()`
382+
- `splitRange()`
383+
- `stringFromColumnIndex()`
384+
385+
### Column index based on 1
386+
387+
Column indexes are now based on 1. So column `A` is the index `1`. This is consistent
388+
with rows starting at 1 and Excel function `COLUMN()` that returns `1` for column `A`.
389+
So the code must be adapted with something like:
390+
391+
```php
392+
// Before
393+
$cell = $worksheet->getCellByColumnAndRow($column, $row);
394+
395+
for ($column = 0; $column < $max; $column++) {
396+
$worksheet->setCellValueByColumnAndRow($column, $row, 'value ' . $column);
397+
}
398+
399+
// After
400+
$cell = $worksheet->getCellByColumnAndRow($column + 1, $row);
401+
402+
for ($column = 1; $column <= $max; $column++) {
403+
$worksheet->setCellValueByColumnAndRow($column, $row, 'value ' . $column);
404+
}
405+
```
406+
407+
All the following methods are affected:
408+
409+
- `PHPExcel_Worksheet::cellExistsByColumnAndRow()`
410+
- `PHPExcel_Worksheet::freezePaneByColumnAndRow()`
411+
- `PHPExcel_Worksheet::getCellByColumnAndRow()`
412+
- `PHPExcel_Worksheet::getColumnDimensionByColumn()`
413+
- `PHPExcel_Worksheet::getCommentByColumnAndRow()`
414+
- `PHPExcel_Worksheet::getStyleByColumnAndRow()`
415+
- `PHPExcel_Worksheet::insertNewColumnBeforeByIndex()`
416+
- `PHPExcel_Worksheet::mergeCellsByColumnAndRow()`
417+
- `PHPExcel_Worksheet::protectCellsByColumnAndRow()`
418+
- `PHPExcel_Worksheet::removeColumnByIndex()`
419+
- `PHPExcel_Worksheet::setAutoFilterByColumnAndRow()`
420+
- `PHPExcel_Worksheet::setBreakByColumnAndRow()`
421+
- `PHPExcel_Worksheet::setCellValueByColumnAndRow()`
422+
- `PHPExcel_Worksheet::setCellValueExplicitByColumnAndRow()`
423+
- `PHPExcel_Worksheet::setSelectedCellByColumnAndRow()`
424+
- `PHPExcel_Worksheet::stringFromColumnIndex()`
425+
- `PHPExcel_Worksheet::unmergeCellsByColumnAndRow()`
426+
- `PHPExcel_Worksheet::unprotectCellsByColumnAndRow()`
427+
- `PHPExcel_Worksheet_PageSetup::addPrintAreaByColumnAndRow()`
428+
- `PHPExcel_Worksheet_PageSetup::setPrintAreaByColumnAndRow()`

samples/Basic/40_Duplicate_style.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
$helper->log('Add data (begin)');
2222
$t = microtime(true);
23-
for ($col = 0; $col < 50; ++$col) {
23+
for ($col = 1; $col <= 50; ++$col) {
2424
for ($row = 0; $row < 100; ++$row) {
2525
$str = ($row + $col);
2626
$style = $styles[$row % 10];

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3496,7 +3496,7 @@ private function processTokenStack($tokens, $cellID = null, Cell $pCell = null)
34963496
$oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1;
34973497
$oRow[] = $oCR[1];
34983498
}
3499-
$cellRef = Coordinate::stringFromColumnIndex(min($oCol)) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol)) . max($oRow);
3499+
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
35003500
if ($pCellParent !== null) {
35013501
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
35023502
} else {
@@ -3569,7 +3569,7 @@ private function processTokenStack($tokens, $cellID = null, Cell $pCell = null)
35693569
$cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]);
35703570
}
35713571
}
3572-
$cellRef = Coordinate::stringFromColumnIndex(min($oCol)) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol)) . max($oRow);
3572+
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
35733573
$this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect));
35743574
$stack->push('Value', $cellIntersect, $cellRef);
35753575

src/PhpSpreadsheet/Calculation/LookupRef.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static function cellAddress($row, $column, $relativity = 1, $referenceSty
5353
}
5454
if ((!is_bool($referenceStyle)) || $referenceStyle) {
5555
$rowRelative = $columnRelative = '$';
56-
$column = Coordinate::stringFromColumnIndex($column - 1);
56+
$column = Coordinate::stringFromColumnIndex($column);
5757
if (($relativity == 2) || ($relativity == 4)) {
5858
$columnRelative = '';
5959
}
@@ -399,7 +399,7 @@ public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $hei
399399
} else {
400400
$endCellColumn += $columns;
401401
}
402-
$startCellColumn = Coordinate::stringFromColumnIndex($startCellColumn);
402+
$startCellColumn = Coordinate::stringFromColumnIndex($startCellColumn + 1);
403403

404404
if (($height != null) && (!is_object($height))) {
405405
$endCellRow = $startCellRow + $height - 1;
@@ -410,7 +410,7 @@ public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $hei
410410
if (($endCellRow <= 0) || ($endCellColumn < 0)) {
411411
return Functions::REF();
412412
}
413-
$endCellColumn = Coordinate::stringFromColumnIndex($endCellColumn);
413+
$endCellColumn = Coordinate::stringFromColumnIndex($endCellColumn + 1);
414414

415415
$cellAddress = $startCellColumn . $startCellRow;
416416
if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) {

src/PhpSpreadsheet/Cell/Coordinate.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
use PhpOffice\PhpSpreadsheet\Exception;
66

7+
/**
8+
* Helper class to manipulate cell coordinates.
9+
*
10+
* Columns indexes and rows are always based on 1, **not** on 0. This match the behavior
11+
* that Excel users are used to, and also match the Excel functions `COLUMN()` and `ROW()`.
12+
*/
713
abstract class Coordinate
814
{
915
/**
@@ -240,7 +246,7 @@ public static function getRangeBoundaries($pRange)
240246
*
241247
* @param string $pString eg 'A'
242248
*
243-
* @return int Column index (base 1 !!!)
249+
* @return int Column index (A = 1)
244250
*/
245251
public static function columnIndexFromString($pString)
246252
{
@@ -284,9 +290,9 @@ public static function columnIndexFromString($pString)
284290
}
285291

286292
/**
287-
* String from columnindex.
293+
* String from column index.
288294
*
289-
* @param int $columnIndex Column index (A = 0)
295+
* @param int $columnIndex Column index (A = 1)
290296
*
291297
* @return string
292298
*/
@@ -295,7 +301,7 @@ public static function stringFromColumnIndex($columnIndex)
295301
static $indexCache = [];
296302

297303
if (!isset($indexCache[$columnIndex])) {
298-
$indexValue = $columnIndex + 1;
304+
$indexValue = $columnIndex;
299305
$base26 = null;
300306
do {
301307
$characterValue = ($indexValue % 26) ?: 26;

src/PhpSpreadsheet/Collection/Cells.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ public function getHighestColumn($row = null)
248248
$columnList[] = Coordinate::columnIndexFromString($c);
249249
}
250250

251-
return Coordinate::stringFromColumnIndex(max($columnList) - 1);
251+
return Coordinate::stringFromColumnIndex(max($columnList) + 1);
252252
}
253253

254254
/**

src/PhpSpreadsheet/Reader/Csv.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ public function listWorksheetInfo($pFilename)
251251
$worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1);
252252
}
253253

254-
$worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex']);
254+
$worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1);
255255
$worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
256256

257257
// Close file

src/PhpSpreadsheet/Reader/Gnumeric.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public function listWorksheetInfo($pFilename)
138138
break;
139139
}
140140
}
141-
$tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex']);
141+
$tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
142142
$worksheetInfo[] = $tmpInfo;
143143
}
144144
}
@@ -394,7 +394,7 @@ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
394394
$maxCol = $column;
395395
}
396396

397-
$column = Coordinate::stringFromColumnIndex($column);
397+
$column = Coordinate::stringFromColumnIndex($column + 1);
398398

399399
// Read cell?
400400
if ($this->getReadFilter() !== null) {
@@ -472,11 +472,11 @@ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
472472
$styleAttributes = $styleRegion->attributes();
473473
if (($styleAttributes['startRow'] <= $maxRow) &&
474474
($styleAttributes['startCol'] <= $maxCol)) {
475-
$startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol']);
475+
$startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1);
476476
$startRow = $styleAttributes['startRow'] + 1;
477477

478478
$endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol'];
479-
$endColumn = Coordinate::stringFromColumnIndex($endColumn);
479+
$endColumn = Coordinate::stringFromColumnIndex($endColumn + 1);
480480
$endRow = ($styleAttributes['endRow'] > $maxRow) ? $maxRow : $styleAttributes['endRow'];
481481
$endRow += 1;
482482
$cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow;
@@ -718,19 +718,19 @@ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
718718
$hidden = ((isset($columnAttributes['Hidden'])) && ($columnAttributes['Hidden'] == '1')) ? true : false;
719719
$columnCount = (isset($columnAttributes['Count'])) ? $columnAttributes['Count'] : 1;
720720
while ($c < $column) {
721-
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setWidth($defaultWidth);
721+
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
722722
++$c;
723723
}
724724
while (($c < ($column + $columnCount)) && ($c <= $maxCol)) {
725-
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setWidth($columnWidth);
725+
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth);
726726
if ($hidden) {
727-
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setVisible(false);
727+
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false);
728728
}
729729
++$c;
730730
}
731731
}
732732
while ($c <= $maxCol) {
733-
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c))->setWidth($defaultWidth);
733+
$spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth);
734734
++$c;
735735
}
736736
}

0 commit comments

Comments
 (0)