Helper class for the conversion of cell addresses between A1 and R1C1 formats, and vice-versa (#1558)

* Helper class for the conversion of cell addresses between A1 and R1C1 formats, and vice-versa
This commit is contained in:
Mark Baker 2020-06-27 23:03:25 +02:00 committed by GitHub
parent 10a4a95d67
commit a264cafe4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 272 additions and 0 deletions

View File

@ -0,0 +1,88 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Exception;
class AddressHelper
{
/**
* Converts an R1C1 format cell address to an A1 format cell address.
*/
public static function convertToA1(
string $address,
int $currentRowNumber = 1,
int $currentColumnNumber = 1
): string {
$validityCheck = preg_match('/^(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))$/i', $address, $cellReference);
if ($validityCheck === 0) {
throw new Exception('Invalid R1C1-format Cell Reference');
}
$rowReference = $cellReference[2];
// Empty R reference is the current row
if ($rowReference === '') {
$rowReference = (string) $currentRowNumber;
}
// Bracketed R references are relative to the current row
if ($rowReference[0] === '[') {
$rowReference = $currentRowNumber + trim($rowReference, '[]');
}
$columnReference = $cellReference[4];
// Empty C reference is the current column
if ($columnReference === '') {
$columnReference = (string) $currentColumnNumber;
}
// Bracketed C references are relative to the current column
if ($columnReference[0] === '[') {
$columnReference = $currentColumnNumber + trim($columnReference, '[]');
}
if ($columnReference <= 0 || $rowReference <= 0) {
throw new Exception('Invalid R1C1-format Cell Reference, Value out of range');
}
$A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference;
return $A1CellReference;
}
/**
* Converts an A1 format cell address to an R1C1 format cell address.
* If $currentRowNumber or $currentColumnNumber are provided, then the R1C1 address will be formatted as a relative address.
*/
public static function convertToR1C1(
string $address,
?int $currentRowNumber = null,
?int $currentColumnNumber = null
): string {
$validityCheck = preg_match('/^\$?([A-Z]{1,3})\$?(\d{1,7})$/i', $address, $cellReference);
if ($validityCheck === 0) {
throw new Exception('Invalid A1-format Cell Reference');
}
$columnId = Coordinate::columnIndexFromString($cellReference[1]);
$rowId = (int) $cellReference[2];
if ($currentRowNumber !== null) {
if ($rowId === $currentRowNumber) {
$rowId = '';
} else {
$rowId = '[' . ($rowId - $currentRowNumber) . ']';
}
}
if ($currentColumnNumber !== null) {
if ($columnId === $currentColumnNumber) {
$columnId = '';
} else {
$columnId = '[' . ($columnId - $currentColumnNumber) . ']';
}
}
$R1C1Address = "R{$rowId}C{$columnId}";
return $R1C1Address;
}
}

View File

@ -0,0 +1,108 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Cell;
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
use PhpOffice\PhpSpreadsheet\Exception;
use PHPUnit\Framework\TestCase;
class AddressHelperTest extends TestCase
{
/**
* @dataProvider providerR1C1ConversionToA1Absolute
*/
public function testR1C1ConversionToA1Absolute(string $expectedValue, string $address): void
{
$actualValue = AddressHelper::convertToA1($address);
self::assertSame($expectedValue, $actualValue);
}
public function providerR1C1ConversionToA1Absolute()
{
return require 'tests/data/Cell/R1C1ConversionToA1Absolute.php';
}
/**
* @dataProvider providerR1C1ConversionToA1Relative
*/
public function testR1C1ConversionToA1Relative(string $expectedValue, string $address, ?int $row = null, ?int $column = null): void
{
$args = [$address];
if ($row !== null) {
$args[] = $row;
}
if ($column !== null) {
$args[] = $column;
}
$actualValue = AddressHelper::convertToA1(...$args);
self::assertSame($expectedValue, $actualValue);
}
public function providerR1C1ConversionToA1Relative()
{
return require 'tests/data/Cell/R1C1ConversionToA1Relative.php';
}
/**
* @dataProvider providerR1C1ConversionToA1Exception
*/
public function testR1C1ConversionToA1Exception(string $address): void
{
$this->expectException(Exception::class);
AddressHelper::convertToA1($address);
}
public function providerR1C1ConversionToA1Exception()
{
return require 'tests/data/Cell/R1C1ConversionToA1Exception.php';
}
/**
* @dataProvider providerA1ConversionToR1C1Absolute
*/
public function testA1ConversionToR1C1Absolute(string $expectedValue, string $address): void
{
$actualValue = AddressHelper::convertToR1C1($address);
self::assertSame($expectedValue, $actualValue);
}
public function providerA1ConversionToR1C1Absolute()
{
return require 'tests/data/Cell/A1ConversionToR1C1Absolute.php';
}
/**
* @dataProvider providerA1ConversionToR1C1Relative
*/
public function testA1ConversionToR1C1Relative(string $expectedValue, string $address, ?int $row = null, ?int $column = null): void
{
$actualValue = AddressHelper::convertToR1C1($address, $row, $column);
self::assertSame($expectedValue, $actualValue);
}
public function providerA1ConversionToR1C1Relative()
{
return require 'tests/data/Cell/A1ConversionToR1C1Relative.php';
}
/**
* @dataProvider providerA1ConversionToR1C1Exception
*/
public function testA1ConversionToR1C1Exception(string $address): void
{
$this->expectException(Exception::class);
AddressHelper::convertToR1C1($address);
}
public function providerA1ConversionToR1C1Exception()
{
return require 'tests/data/Cell/A1ConversionToR1C1Exception.php';
}
}

View File

@ -0,0 +1,15 @@
<?php
return [
['R1C1', 'A1'],
['R1C1', '$A$1'],
['R1C1', '$A1'],
['R1C1', 'A$1'],
['R2C2', 'B2'],
['R5C12', 'L5'],
['R1024C26', 'Z1024'],
['R2048C27', 'AA2048'],
['R4096C52', 'AZ4096'],
['R8192C53', 'BA8192'],
['R65535C256', 'IV65535'],
];

View File

@ -0,0 +1,5 @@
<?php
return [
['A11Y'],
];

View File

@ -0,0 +1,19 @@
<?php
return [
['R[2]C[2]', 'O18', 16, 13],
['R[-2]C[2]', 'O14', 16, 13],
['R[2]C[-2]', 'K18', 16, 13],
['R[-2]C[-2]', 'K14', 16, 13],
['RC[3]', 'P16', 16, 13],
['RC[-3]', 'J16', 16, 13],
['R[4]C', 'M20', 16, 13],
['R[-4]C', 'M12', 16, 13],
['RC', 'E5', 5, 5],
['R5C', 'E5', null, 5],
['RC5', 'E5', 5, null],
['R5C[2]', 'E5', null, 3],
['R[2]C5', 'E5', 3, null],
['R5C[-2]', 'E5', null, 7],
['R[-2]C5', 'E5', 7, null],
];

View File

@ -0,0 +1,14 @@
<?php
return [
['A1', 'R1C1'],
['B2', 'R2C2'],
['L5', 'R5C12'],
['Z1024', 'R1024C26'],
['AA2048', 'R2048C27'],
['AZ4096', 'R4096C52'],
['BA8192', 'R8192C53'],
['IV65535', 'R65535C256'],
['K1', 'RC11'],
['A16', 'R16C'],
];

View File

@ -0,0 +1,9 @@
<?php
return [
['RICE'],
['R[-1]C'],
['RC[-1]'],
['R-1C'],
['RC-1'],
];

View File

@ -0,0 +1,14 @@
<?php
return [
['C3', 'R[2]C[2]'],
['O18', 'R[2]C[2]', 16, 13],
['O14', 'R[-2]C[2]', 16, 13],
['K18', 'R[2]C[-2]', 16, 13],
['K14', 'R[-2]C[-2]', 16, 13],
['P16', 'RC[3]', 16, 13],
['J16', 'RC[-3]', 16, 13],
['M20', 'R[4]C', 16, 13],
['M12', 'R[-4]C', 16, 13],
['E5', 'RC', 5, 5],
];