Start refactoring the Lookup and Reference functions (#1912)
* Start refactoring the Lookup and Reference functions - COLUMN(), COLUMNS(), ROW() and ROWS() - LOOKUP(), VLOOKUP() and HLOOKUP() - Refactor TRANSPOSE() and ADDRESS() functions into their own classes * Additional unit tests - LOOKUP() - TRANSPOSE() - ADDRESS()
This commit is contained in:
parent
f81ffd9a4f
commit
70f372d88c
|
|
@ -263,7 +263,7 @@ class Calculation
|
|||
],
|
||||
'ADDRESS' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'cellAddress'],
|
||||
'functionCall' => [LookupRef\Address::class, 'cell'],
|
||||
'argumentCount' => '2-5',
|
||||
],
|
||||
'AGGREGATE' => [
|
||||
|
|
@ -543,13 +543,14 @@ class Calculation
|
|||
],
|
||||
'COLUMN' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'COLUMN'],
|
||||
'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMN'],
|
||||
'argumentCount' => '-1',
|
||||
'passCellReference' => true,
|
||||
'passByReference' => [true],
|
||||
],
|
||||
'COLUMNS' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'COLUMNS'],
|
||||
'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMNS'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'COMBIN' => [
|
||||
|
|
@ -1231,7 +1232,7 @@ class Calculation
|
|||
],
|
||||
'HLOOKUP' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'HLOOKUP'],
|
||||
'functionCall' => [LookupRef\HLookup::class, 'lookup'],
|
||||
'argumentCount' => '3,4',
|
||||
],
|
||||
'HOUR' => [
|
||||
|
|
@ -1605,7 +1606,7 @@ class Calculation
|
|||
],
|
||||
'LOOKUP' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'LOOKUP'],
|
||||
'functionCall' => [LookupRef\Lookup::class, 'lookup'],
|
||||
'argumentCount' => '2,3',
|
||||
],
|
||||
'LOWER' => [
|
||||
|
|
@ -2127,13 +2128,14 @@ class Calculation
|
|||
],
|
||||
'ROW' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'ROW'],
|
||||
'functionCall' => [LookupRef\RowColumnInformation::class, 'ROW'],
|
||||
'argumentCount' => '-1',
|
||||
'passCellReference' => true,
|
||||
'passByReference' => [true],
|
||||
],
|
||||
'ROWS' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'ROWS'],
|
||||
'functionCall' => [LookupRef\RowColumnInformation::class, 'ROWS'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'RRI' => [
|
||||
|
|
@ -2449,7 +2451,7 @@ class Calculation
|
|||
],
|
||||
'TRANSPOSE' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'TRANSPOSE'],
|
||||
'functionCall' => [LookupRef\Matrix::class, 'transpose'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'TREND' => [
|
||||
|
|
@ -2559,7 +2561,7 @@ class Calculation
|
|||
],
|
||||
'VLOOKUP' => [
|
||||
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
|
||||
'functionCall' => [LookupRef::class, 'VLOOKUP'],
|
||||
'functionCall' => [LookupRef\VLookup::class, 'lookup'],
|
||||
'argumentCount' => '3,4',
|
||||
],
|
||||
'WEBSERVICE' => [
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Address;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\HLookup;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Lookup;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\RowColumnInformation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\VLookup;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
|
@ -17,15 +23,19 @@ class LookupRef
|
|||
* Excel Function:
|
||||
* =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText])
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the cell() method in the LookupRef\Address class instead
|
||||
*
|
||||
* @param mixed $row Row number to use in the cell reference
|
||||
* @param mixed $column Column number to use in the cell reference
|
||||
* @param int $relativity Flag indicating the type of reference to return
|
||||
* 1 or omitted Absolute
|
||||
* 2 Absolute row; relative column
|
||||
* 3 Relative row; absolute column
|
||||
* 4 Relative
|
||||
* 2 Absolute row; relative column
|
||||
* 3 Relative row; absolute column
|
||||
* 4 Relative
|
||||
* @param bool $referenceStyle A logical value that specifies the A1 or R1C1 reference style.
|
||||
* TRUE or omitted CELL_ADDRESS returns an A1-style reference
|
||||
* TRUE or omitted CELL_ADDRESS returns an A1-style reference
|
||||
* FALSE CELL_ADDRESS returns an R1C1-style reference
|
||||
* @param string $sheetText Optional Name of worksheet to use
|
||||
*
|
||||
|
|
@ -33,87 +43,33 @@ class LookupRef
|
|||
*/
|
||||
public static function cellAddress($row, $column, $relativity = 1, $referenceStyle = true, $sheetText = '')
|
||||
{
|
||||
$row = Functions::flattenSingleValue($row);
|
||||
$column = Functions::flattenSingleValue($column);
|
||||
$relativity = Functions::flattenSingleValue($relativity);
|
||||
$sheetText = Functions::flattenSingleValue($sheetText);
|
||||
|
||||
if (($row < 1) || ($column < 1)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
if ($sheetText > '') {
|
||||
if (strpos($sheetText, ' ') !== false) {
|
||||
$sheetText = "'" . $sheetText . "'";
|
||||
}
|
||||
$sheetText .= '!';
|
||||
}
|
||||
if ((!is_bool($referenceStyle)) || $referenceStyle) {
|
||||
$rowRelative = $columnRelative = '$';
|
||||
$column = Coordinate::stringFromColumnIndex($column);
|
||||
if (($relativity == 2) || ($relativity == 4)) {
|
||||
$columnRelative = '';
|
||||
}
|
||||
if (($relativity == 3) || ($relativity == 4)) {
|
||||
$rowRelative = '';
|
||||
}
|
||||
|
||||
return $sheetText . $columnRelative . $column . $rowRelative . $row;
|
||||
}
|
||||
if (($relativity == 2) || ($relativity == 4)) {
|
||||
$column = '[' . $column . ']';
|
||||
}
|
||||
if (($relativity == 3) || ($relativity == 4)) {
|
||||
$row = '[' . $row . ']';
|
||||
}
|
||||
|
||||
return $sheetText . 'R' . $row . 'C' . $column;
|
||||
return Address::cell($row, $column, $relativity, $referenceStyle, $sheetText);
|
||||
}
|
||||
|
||||
/**
|
||||
* COLUMN.
|
||||
*
|
||||
* Returns the column number of the given cell reference
|
||||
* If the cell reference is a range of cells, COLUMN returns the column numbers of each column in the reference as a horizontal array.
|
||||
* If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
|
||||
* reference of the cell in which the COLUMN function appears; otherwise this function returns 0.
|
||||
* If the cell reference is a range of cells, COLUMN returns the column numbers of each column
|
||||
* in the reference as a horizontal array.
|
||||
* If cell reference is omitted, and the function is being called through the calculation engine,
|
||||
* then it is assumed to be the reference of the cell in which the COLUMN function appears;
|
||||
* otherwise this function returns 1.
|
||||
*
|
||||
* Excel Function:
|
||||
* =COLUMN([cellAddress])
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the COLUMN() method in the LookupRef\RowColumnInformation class instead
|
||||
*
|
||||
* @param null|array|string $cellAddress A reference to a range of cells for which you want the column numbers
|
||||
*
|
||||
* @return int|int[]
|
||||
*/
|
||||
public static function COLUMN($cellAddress = null)
|
||||
public static function COLUMN($cellAddress = null, ?Cell $cell = null)
|
||||
{
|
||||
if ($cellAddress === null || trim($cellAddress) === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (is_array($cellAddress)) {
|
||||
foreach ($cellAddress as $columnKey => $value) {
|
||||
$columnKey = preg_replace('/[^a-z]/i', '', $columnKey);
|
||||
|
||||
return (int) Coordinate::columnIndexFromString($columnKey);
|
||||
}
|
||||
} else {
|
||||
[$sheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||
if (strpos($cellAddress, ':') !== false) {
|
||||
[$startAddress, $endAddress] = explode(':', $cellAddress);
|
||||
$startAddress = preg_replace('/[^a-z]/i', '', $startAddress);
|
||||
$endAddress = preg_replace('/[^a-z]/i', '', $endAddress);
|
||||
$returnValue = [];
|
||||
do {
|
||||
$returnValue[] = (int) Coordinate::columnIndexFromString($startAddress);
|
||||
} while ($startAddress++ != $endAddress);
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
$cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress);
|
||||
|
||||
return (int) Coordinate::columnIndexFromString($cellAddress);
|
||||
}
|
||||
return RowColumnInformation::COLUMN($cellAddress, $cell);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -124,73 +80,44 @@ class LookupRef
|
|||
* Excel Function:
|
||||
* =COLUMNS(cellAddress)
|
||||
*
|
||||
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells for which you want the number of columns
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the COLUMNS() method in the LookupRef\RowColumnInformation class instead
|
||||
*
|
||||
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells
|
||||
* for which you want the number of columns
|
||||
*
|
||||
* @return int|string The number of columns in cellAddress, or a string if arguments are invalid
|
||||
*/
|
||||
public static function COLUMNS($cellAddress = null)
|
||||
{
|
||||
if ($cellAddress === null || $cellAddress === '') {
|
||||
return 1;
|
||||
} elseif (!is_array($cellAddress)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
reset($cellAddress);
|
||||
$isMatrix = (is_numeric(key($cellAddress)));
|
||||
[$columns, $rows] = Calculation::getMatrixDimensions($cellAddress);
|
||||
|
||||
if ($isMatrix) {
|
||||
return $rows;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
return RowColumnInformation::COLUMNS($cellAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* ROW.
|
||||
*
|
||||
* Returns the row number of the given cell reference
|
||||
* If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference as a vertical array.
|
||||
* If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
|
||||
* reference of the cell in which the ROW function appears; otherwise this function returns 0.
|
||||
* If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference
|
||||
* as a vertical array.
|
||||
* If cell reference is omitted, and the function is being called through the calculation engine,
|
||||
* then it is assumed to be the reference of the cell in which the ROW function appears;
|
||||
* otherwise this function returns 1.
|
||||
*
|
||||
* Excel Function:
|
||||
* =ROW([cellAddress])
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the ROW() method in the LookupRef\RowColumnInformation class instead
|
||||
*
|
||||
* @param null|array|string $cellAddress A reference to a range of cells for which you want the row numbers
|
||||
*
|
||||
* @return int|mixed[]|string
|
||||
*/
|
||||
public static function ROW($cellAddress = null)
|
||||
public static function ROW($cellAddress = null, ?Cell $cell = null)
|
||||
{
|
||||
if ($cellAddress === null || trim($cellAddress) === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (is_array($cellAddress)) {
|
||||
foreach ($cellAddress as $columnKey => $rowValue) {
|
||||
foreach ($rowValue as $rowKey => $cellValue) {
|
||||
return (int) preg_replace('/\D/', '', $rowKey);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
[$sheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||
if (strpos($cellAddress, ':') !== false) {
|
||||
[$startAddress, $endAddress] = explode(':', $cellAddress);
|
||||
$startAddress = preg_replace('/\D/', '', $startAddress);
|
||||
$endAddress = preg_replace('/\D/', '', $endAddress);
|
||||
$returnValue = [];
|
||||
do {
|
||||
$returnValue[][] = (int) $startAddress;
|
||||
} while ($startAddress++ != $endAddress);
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
[$cellAddress] = explode(':', $cellAddress);
|
||||
|
||||
return (int) preg_replace('/\D/', '', $cellAddress);
|
||||
}
|
||||
return RowColumnInformation::ROW($cellAddress, $cell);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -201,27 +128,18 @@ class LookupRef
|
|||
* Excel Function:
|
||||
* =ROWS(cellAddress)
|
||||
*
|
||||
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells for which you want the number of rows
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the ROWS() method in the LookupRef\RowColumnInformation class instead
|
||||
*
|
||||
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells
|
||||
* for which you want the number of rows
|
||||
*
|
||||
* @return int|string The number of rows in cellAddress, or a string if arguments are invalid
|
||||
*/
|
||||
public static function ROWS($cellAddress = null)
|
||||
{
|
||||
if ($cellAddress === null || $cellAddress === '') {
|
||||
return 1;
|
||||
} elseif (!is_array($cellAddress)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
reset($cellAddress);
|
||||
$isMatrix = (is_numeric(key($cellAddress)));
|
||||
[$columns, $rows] = Calculation::getMatrixDimensions($cellAddress);
|
||||
|
||||
if ($isMatrix) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
return RowColumnInformation::ROWS($cellAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -669,204 +587,74 @@ class LookupRef
|
|||
/**
|
||||
* TRANSPOSE.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the transpose() method in the LookupRef\Matrix class instead
|
||||
*
|
||||
* @param array $matrixData A matrix of values
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix
|
||||
* Unlike the Excel TRANSPOSE function, which will only work on a single row or column,
|
||||
* this function will transpose a full matrix
|
||||
*/
|
||||
public static function TRANSPOSE($matrixData)
|
||||
{
|
||||
$returnMatrix = [];
|
||||
if (!is_array($matrixData)) {
|
||||
$matrixData = [[$matrixData]];
|
||||
}
|
||||
|
||||
$column = 0;
|
||||
foreach ($matrixData as $matrixRow) {
|
||||
$row = 0;
|
||||
foreach ($matrixRow as $matrixCell) {
|
||||
$returnMatrix[$row][$column] = $matrixCell;
|
||||
++$row;
|
||||
}
|
||||
++$column;
|
||||
}
|
||||
|
||||
return $returnMatrix;
|
||||
}
|
||||
|
||||
private static function vlookupSort($a, $b)
|
||||
{
|
||||
reset($a);
|
||||
$firstColumn = key($a);
|
||||
$aLower = StringHelper::strToLower($a[$firstColumn]);
|
||||
$bLower = StringHelper::strToLower($b[$firstColumn]);
|
||||
if ($aLower == $bLower) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($aLower < $bLower) ? -1 : 1;
|
||||
return Matrix::transpose($matrixData);
|
||||
}
|
||||
|
||||
/**
|
||||
* VLOOKUP
|
||||
* The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value in the same row based on the index_number.
|
||||
* The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value
|
||||
* in the same row based on the index_number.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the lookup() method in the LookupRef\VLookup class instead
|
||||
*
|
||||
* @param mixed $lookup_value The value that you want to match in lookup_array
|
||||
* @param mixed $lookup_array The range of cells being searched
|
||||
* @param mixed $index_number The column number in table_array from which the matching value must be returned. The first column is 1.
|
||||
* @param mixed $index_number The column number in table_array from which the matching value must be returned.
|
||||
* The first column is 1.
|
||||
* @param mixed $not_exact_match determines if you are looking for an exact match based on lookup_value
|
||||
*
|
||||
* @return mixed The value of the found cell
|
||||
*/
|
||||
public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true)
|
||||
{
|
||||
$lookup_value = Functions::flattenSingleValue($lookup_value);
|
||||
$index_number = Functions::flattenSingleValue($index_number);
|
||||
$not_exact_match = Functions::flattenSingleValue($not_exact_match);
|
||||
|
||||
// index_number must be greater than or equal to 1
|
||||
if ($index_number < 1) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
// index_number must be less than or equal to the number of columns in lookup_array
|
||||
if ((!is_array($lookup_array)) || (empty($lookup_array))) {
|
||||
return Functions::REF();
|
||||
}
|
||||
$f = array_keys($lookup_array);
|
||||
$firstRow = array_pop($f);
|
||||
if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array[$firstRow]))) {
|
||||
return Functions::REF();
|
||||
}
|
||||
$columnKeys = array_keys($lookup_array[$firstRow]);
|
||||
$returnColumn = $columnKeys[--$index_number];
|
||||
$firstColumn = array_shift($columnKeys);
|
||||
|
||||
if (!$not_exact_match) {
|
||||
uasort($lookup_array, ['self', 'vlookupSort']);
|
||||
}
|
||||
|
||||
$lookupLower = StringHelper::strToLower($lookup_value);
|
||||
$rowNumber = $rowValue = false;
|
||||
foreach ($lookup_array as $rowKey => $rowData) {
|
||||
$firstLower = StringHelper::strToLower($rowData[$firstColumn]);
|
||||
|
||||
// break if we have passed possible keys
|
||||
if (
|
||||
(is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) ||
|
||||
(!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && ($firstLower > $lookupLower))
|
||||
) {
|
||||
break;
|
||||
}
|
||||
// remember the last key, but only if datatypes match
|
||||
if (
|
||||
(is_numeric($lookup_value) && is_numeric($rowData[$firstColumn])) ||
|
||||
(!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]))
|
||||
) {
|
||||
if ($not_exact_match) {
|
||||
$rowNumber = $rowKey;
|
||||
|
||||
continue;
|
||||
} elseif (
|
||||
($firstLower == $lookupLower)
|
||||
// Spreadsheets software returns first exact match,
|
||||
// we have sorted and we might have broken key orders
|
||||
// we want the first one (by its initial index)
|
||||
&& (($rowNumber == false) || ($rowKey < $rowNumber))
|
||||
) {
|
||||
$rowNumber = $rowKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($rowNumber !== false) {
|
||||
// return the appropriate value
|
||||
return $lookup_array[$rowNumber][$returnColumn];
|
||||
}
|
||||
|
||||
return Functions::NA();
|
||||
return VLookup::lookup($lookup_value, $lookup_array, $index_number, $not_exact_match);
|
||||
}
|
||||
|
||||
/**
|
||||
* HLOOKUP
|
||||
* The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value in the same column based on the index_number.
|
||||
* The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value
|
||||
* in the same column based on the index_number.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the lookup() method in the LookupRef\HLookup class instead
|
||||
*
|
||||
* @param mixed $lookup_value The value that you want to match in lookup_array
|
||||
* @param mixed $lookup_array The range of cells being searched
|
||||
* @param mixed $index_number The row number in table_array from which the matching value must be returned. The first row is 1.
|
||||
* @param mixed $index_number The row number in table_array from which the matching value must be returned.
|
||||
* The first row is 1.
|
||||
* @param mixed $not_exact_match determines if you are looking for an exact match based on lookup_value
|
||||
*
|
||||
* @return mixed The value of the found cell
|
||||
*/
|
||||
public static function HLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true)
|
||||
{
|
||||
$lookup_value = Functions::flattenSingleValue($lookup_value);
|
||||
$index_number = Functions::flattenSingleValue($index_number);
|
||||
$not_exact_match = Functions::flattenSingleValue($not_exact_match);
|
||||
|
||||
// index_number must be greater than or equal to 1
|
||||
if ($index_number < 1) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
// index_number must be less than or equal to the number of columns in lookup_array
|
||||
if ((!is_array($lookup_array)) || (empty($lookup_array))) {
|
||||
return Functions::REF();
|
||||
}
|
||||
$f = array_keys($lookup_array);
|
||||
$firstRow = reset($f);
|
||||
if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array))) {
|
||||
return Functions::REF();
|
||||
}
|
||||
|
||||
$firstkey = $f[0] - 1;
|
||||
$returnColumn = $firstkey + $index_number;
|
||||
$firstColumn = array_shift($f);
|
||||
$rowNumber = null;
|
||||
foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) {
|
||||
// break if we have passed possible keys
|
||||
$bothNumeric = is_numeric($lookup_value) && is_numeric($rowData);
|
||||
$bothNotNumeric = !is_numeric($lookup_value) && !is_numeric($rowData);
|
||||
$lookupLower = StringHelper::strToLower($lookup_value);
|
||||
$rowDataLower = StringHelper::strToLower($rowData);
|
||||
|
||||
if (
|
||||
$not_exact_match && (
|
||||
($bothNumeric && $rowData > $lookup_value) ||
|
||||
($bothNotNumeric && $rowDataLower > $lookupLower)
|
||||
)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Remember the last key, but only if datatypes match (as in VLOOKUP)
|
||||
if ($bothNumeric || $bothNotNumeric) {
|
||||
if ($not_exact_match) {
|
||||
$rowNumber = $rowKey;
|
||||
|
||||
continue;
|
||||
} elseif (
|
||||
$rowDataLower === $lookupLower
|
||||
&& ($rowNumber === null || $rowKey < $rowNumber)
|
||||
) {
|
||||
$rowNumber = $rowKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($rowNumber !== null) {
|
||||
// otherwise return the appropriate value
|
||||
return $lookup_array[$returnColumn][$rowNumber];
|
||||
}
|
||||
|
||||
return Functions::NA();
|
||||
return HLookup::lookup($lookup_value, $lookup_array, $index_number, $not_exact_match);
|
||||
}
|
||||
|
||||
/**
|
||||
* LOOKUP
|
||||
* The LOOKUP function searches for value either from a one-row or one-column range or from an array.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the lookup() method in the LookupRef\Lookup class instead
|
||||
*
|
||||
* @param mixed $lookup_value The value that you want to match in lookup_array
|
||||
* @param mixed $lookup_vector The range of cells being searched
|
||||
* @param null|mixed $result_vector The column from which the matching value must be returned
|
||||
|
|
@ -875,66 +663,7 @@ class LookupRef
|
|||
*/
|
||||
public static function LOOKUP($lookup_value, $lookup_vector, $result_vector = null)
|
||||
{
|
||||
$lookup_value = Functions::flattenSingleValue($lookup_value);
|
||||
|
||||
if (!is_array($lookup_vector)) {
|
||||
return Functions::NA();
|
||||
}
|
||||
$hasResultVector = isset($result_vector);
|
||||
$lookupRows = count($lookup_vector);
|
||||
$l = array_keys($lookup_vector);
|
||||
$l = array_shift($l);
|
||||
$lookupColumns = count($lookup_vector[$l]);
|
||||
// we correctly orient our results
|
||||
if (($lookupRows === 1 && $lookupColumns > 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) {
|
||||
$lookup_vector = self::TRANSPOSE($lookup_vector);
|
||||
$lookupRows = count($lookup_vector);
|
||||
$l = array_keys($lookup_vector);
|
||||
$lookupColumns = count($lookup_vector[array_shift($l)]);
|
||||
}
|
||||
|
||||
if ($result_vector === null) {
|
||||
$result_vector = $lookup_vector;
|
||||
}
|
||||
$resultRows = count($result_vector);
|
||||
$l = array_keys($result_vector);
|
||||
$l = array_shift($l);
|
||||
$resultColumns = count($result_vector[$l]);
|
||||
// we correctly orient our results
|
||||
if ($resultRows === 1 && $resultColumns > 1) {
|
||||
$result_vector = self::TRANSPOSE($result_vector);
|
||||
$resultRows = count($result_vector);
|
||||
$r = array_keys($result_vector);
|
||||
$resultColumns = count($result_vector[array_shift($r)]);
|
||||
}
|
||||
|
||||
if ($lookupRows === 2 && !$hasResultVector) {
|
||||
$result_vector = array_pop($lookup_vector);
|
||||
$lookup_vector = array_shift($lookup_vector);
|
||||
}
|
||||
|
||||
if ($lookupColumns !== 2) {
|
||||
foreach ($lookup_vector as &$value) {
|
||||
if (is_array($value)) {
|
||||
$k = array_keys($value);
|
||||
$key1 = $key2 = array_shift($k);
|
||||
++$key2;
|
||||
$dataValue1 = $value[$key1];
|
||||
} else {
|
||||
$key1 = 0;
|
||||
$key2 = 1;
|
||||
$dataValue1 = $value;
|
||||
}
|
||||
$dataValue2 = array_shift($result_vector);
|
||||
if (is_array($dataValue2)) {
|
||||
$dataValue2 = array_shift($dataValue2);
|
||||
}
|
||||
$value = [$key1 => $dataValue1, $key2 => $dataValue2];
|
||||
}
|
||||
unset($value);
|
||||
}
|
||||
|
||||
return self::VLOOKUP($lookup_value, $lookup_vector, 2);
|
||||
return Lookup::lookup($lookup_value, $lookup_vector, $result_vector);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
|
||||
class Address
|
||||
{
|
||||
public const ADDRESS_ABSOLUTE = 1;
|
||||
public const ADDRESS_COLUMN_RELATIVE = 2;
|
||||
public const ADDRESS_ROW_RELATIVE = 3;
|
||||
public const ADDRESS_RELATIVE = 4;
|
||||
|
||||
public const REFERENCE_STYLE_A1 = true;
|
||||
public const REFERENCE_STYLE_R1C1 = false;
|
||||
|
||||
/**
|
||||
* ADDRESS.
|
||||
*
|
||||
* Creates a cell address as text, given specified row and column numbers.
|
||||
*
|
||||
* Excel Function:
|
||||
* =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText])
|
||||
*
|
||||
* @param mixed $row Row number to use in the cell reference
|
||||
* @param mixed $column Column number to use in the cell reference
|
||||
* @param int $relativity Flag indicating the type of reference to return
|
||||
* 1 or omitted Absolute
|
||||
* 2 Absolute row; relative column
|
||||
* 3 Relative row; absolute column
|
||||
* 4 Relative
|
||||
* @param bool $referenceStyle A logical value that specifies the A1 or R1C1 reference style.
|
||||
* TRUE or omitted ADDRESS returns an A1-style reference
|
||||
* FALSE ADDRESS returns an R1C1-style reference
|
||||
* @param string $sheetName Optional Name of worksheet to use
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function cell($row, $column, $relativity = 1, $referenceStyle = true, $sheetName = '')
|
||||
{
|
||||
$row = Functions::flattenSingleValue($row);
|
||||
$column = Functions::flattenSingleValue($column);
|
||||
$relativity = Functions::flattenSingleValue($relativity);
|
||||
$sheetName = Functions::flattenSingleValue($sheetName);
|
||||
|
||||
if (($row < 1) || ($column < 1)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
$sheetName = self::sheetName($sheetName);
|
||||
|
||||
if ((!is_bool($referenceStyle)) || $referenceStyle === self::REFERENCE_STYLE_A1) {
|
||||
return self::formatAsA1($row, $column, $relativity, $sheetName);
|
||||
}
|
||||
|
||||
return self::formatAsR1C1($row, $column, $relativity, $sheetName);
|
||||
}
|
||||
|
||||
private static function sheetName(string $sheetName)
|
||||
{
|
||||
if ($sheetName > '') {
|
||||
if (strpos($sheetName, ' ') !== false || strpos($sheetName, '[') !== false) {
|
||||
$sheetName = "'{$sheetName}'";
|
||||
}
|
||||
$sheetName .= '!';
|
||||
}
|
||||
|
||||
return $sheetName;
|
||||
}
|
||||
|
||||
private static function formatAsA1(int $row, int $column, int $relativity, string $sheetName): string
|
||||
{
|
||||
$rowRelative = $columnRelative = '$';
|
||||
if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) {
|
||||
$columnRelative = '';
|
||||
}
|
||||
if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) {
|
||||
$rowRelative = '';
|
||||
}
|
||||
$column = Coordinate::stringFromColumnIndex($column);
|
||||
|
||||
return "{$sheetName}{$columnRelative}{$column}{$rowRelative}{$row}";
|
||||
}
|
||||
|
||||
private static function formatAsR1C1(int $row, int $column, int $relativity, string $sheetName): string
|
||||
{
|
||||
if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) {
|
||||
$column = "[{$column}]";
|
||||
}
|
||||
if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) {
|
||||
$row = "[{$row}]";
|
||||
}
|
||||
|
||||
return "{$sheetName}R{$row}C{$column}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class HLookup extends LookupBase
|
||||
{
|
||||
/**
|
||||
* HLOOKUP
|
||||
* The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value
|
||||
* in the same column based on the index_number.
|
||||
*
|
||||
* @param mixed $lookupValue The value that you want to match in lookup_array
|
||||
* @param mixed $lookupArray The range of cells being searched
|
||||
* @param mixed $indexNumber The row number in table_array from which the matching value must be returned.
|
||||
* The first row is 1.
|
||||
* @param mixed $notExactMatch determines if you are looking for an exact match based on lookup_value
|
||||
*
|
||||
* @return mixed The value of the found cell
|
||||
*/
|
||||
public static function lookup($lookupValue, $lookupArray, $indexNumber, $notExactMatch = true)
|
||||
{
|
||||
$lookupValue = Functions::flattenSingleValue($lookupValue);
|
||||
$indexNumber = Functions::flattenSingleValue($indexNumber);
|
||||
$notExactMatch = Functions::flattenSingleValue($notExactMatch);
|
||||
|
||||
try {
|
||||
$indexNumber = self::validateIndexLookup($lookupArray, $indexNumber);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$f = array_keys($lookupArray);
|
||||
$firstRow = reset($f);
|
||||
if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray))) {
|
||||
return Functions::REF();
|
||||
}
|
||||
|
||||
$firstkey = $f[0] - 1;
|
||||
$returnColumn = $firstkey + $indexNumber;
|
||||
$firstColumn = array_shift($f);
|
||||
$rowNumber = self::hLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch);
|
||||
|
||||
if ($rowNumber !== null) {
|
||||
// otherwise return the appropriate value
|
||||
return $lookupArray[$returnColumn][$rowNumber];
|
||||
}
|
||||
|
||||
return Functions::NA();
|
||||
}
|
||||
|
||||
private static function hLookupSearch($lookupValue, $lookupArray, $column, $notExactMatch)
|
||||
{
|
||||
$lookupLower = StringHelper::strToLower($lookupValue);
|
||||
|
||||
$rowNumber = null;
|
||||
foreach ($lookupArray[$column] as $rowKey => $rowData) {
|
||||
// break if we have passed possible keys
|
||||
$bothNumeric = is_numeric($lookupValue) && is_numeric($rowData);
|
||||
$bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData);
|
||||
$cellDataLower = StringHelper::strToLower($rowData);
|
||||
|
||||
if (
|
||||
$notExactMatch &&
|
||||
(($bothNumeric && $rowData > $lookupValue) || ($bothNotNumeric && $cellDataLower > $lookupLower))
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
$rowNumber = self::checkMatch(
|
||||
$bothNumeric,
|
||||
$bothNotNumeric,
|
||||
$notExactMatch,
|
||||
$rowKey,
|
||||
$cellDataLower,
|
||||
$lookupLower,
|
||||
$rowNumber
|
||||
);
|
||||
}
|
||||
|
||||
return $rowNumber;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
class Lookup
|
||||
{
|
||||
/**
|
||||
* LOOKUP
|
||||
* The LOOKUP function searches for value either from a one-row or one-column range or from an array.
|
||||
*
|
||||
* @param mixed $lookupValue The value that you want to match in lookup_array
|
||||
* @param mixed $lookupVector The range of cells being searched
|
||||
* @param null|mixed $resultVector The column from which the matching value must be returned
|
||||
*
|
||||
* @return mixed The value of the found cell
|
||||
*/
|
||||
public static function lookup($lookupValue, $lookupVector, $resultVector = null)
|
||||
{
|
||||
$lookupValue = Functions::flattenSingleValue($lookupValue);
|
||||
|
||||
if (!is_array($lookupVector)) {
|
||||
return Functions::NA();
|
||||
}
|
||||
$hasResultVector = isset($resultVector);
|
||||
$lookupRows = self::rowCount($lookupVector);
|
||||
$lookupColumns = self::columnCount($lookupVector);
|
||||
// we correctly orient our results
|
||||
if (($lookupRows === 1 && $lookupColumns > 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) {
|
||||
$lookupVector = LookupRef::TRANSPOSE($lookupVector);
|
||||
$lookupRows = self::rowCount($lookupVector);
|
||||
$lookupColumns = self::columnCount($lookupVector);
|
||||
}
|
||||
|
||||
$resultVector = self::verifyResultVector($lookupVector, $resultVector);
|
||||
|
||||
if ($lookupRows === 2 && !$hasResultVector) {
|
||||
$resultVector = array_pop($lookupVector);
|
||||
$lookupVector = array_shift($lookupVector);
|
||||
}
|
||||
|
||||
if ($lookupColumns !== 2) {
|
||||
$lookupVector = self::verifyLookupValues($lookupVector, $resultVector);
|
||||
}
|
||||
|
||||
return VLookup::lookup($lookupValue, $lookupVector, 2);
|
||||
}
|
||||
|
||||
private static function verifyLookupValues(array $lookupVector, array $resultVector): array
|
||||
{
|
||||
foreach ($lookupVector as &$value) {
|
||||
if (is_array($value)) {
|
||||
$k = array_keys($value);
|
||||
$key1 = $key2 = array_shift($k);
|
||||
++$key2;
|
||||
$dataValue1 = $value[$key1];
|
||||
} else {
|
||||
$key1 = 0;
|
||||
$key2 = 1;
|
||||
$dataValue1 = $value;
|
||||
}
|
||||
|
||||
$dataValue2 = array_shift($resultVector);
|
||||
if (is_array($dataValue2)) {
|
||||
$dataValue2 = array_shift($dataValue2);
|
||||
}
|
||||
$value = [$key1 => $dataValue1, $key2 => $dataValue2];
|
||||
}
|
||||
unset($value);
|
||||
|
||||
return $lookupVector;
|
||||
}
|
||||
|
||||
private static function verifyResultVector(array $lookupVector, $resultVector)
|
||||
{
|
||||
if ($resultVector === null) {
|
||||
$resultVector = $lookupVector;
|
||||
}
|
||||
|
||||
$resultRows = self::rowCount($resultVector);
|
||||
$resultColumns = self::columnCount($resultVector);
|
||||
|
||||
// we correctly orient our results
|
||||
if ($resultRows === 1 && $resultColumns > 1) {
|
||||
$resultVector = LookupRef::TRANSPOSE($resultVector);
|
||||
}
|
||||
|
||||
return $resultVector;
|
||||
}
|
||||
|
||||
private static function rowCount(array $dataArray): int
|
||||
{
|
||||
return count($dataArray);
|
||||
}
|
||||
|
||||
private static function columnCount(array $dataArray): int
|
||||
{
|
||||
$rowKeys = array_keys($dataArray);
|
||||
$row = array_shift($rowKeys);
|
||||
|
||||
return count($dataArray[$row]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
abstract class LookupBase
|
||||
{
|
||||
protected static function validateIndexLookup($lookup_array, $index_number)
|
||||
{
|
||||
// index_number must be a number greater than or equal to 1
|
||||
if (!is_numeric($index_number) || $index_number < 1) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
// index_number must be less than or equal to the number of columns in lookup_array
|
||||
if ((!is_array($lookup_array)) || (empty($lookup_array))) {
|
||||
throw new Exception(Functions::REF());
|
||||
}
|
||||
|
||||
return (int) $index_number;
|
||||
}
|
||||
|
||||
protected static function checkMatch(
|
||||
bool $bothNumeric,
|
||||
bool $bothNotNumeric,
|
||||
$notExactMatch,
|
||||
int $rowKey,
|
||||
string $cellDataLower,
|
||||
string $lookupLower,
|
||||
?int $rowNumber
|
||||
): ?int {
|
||||
// remember the last key, but only if datatypes match
|
||||
if ($bothNumeric || $bothNotNumeric) {
|
||||
// Spreadsheets software returns first exact match,
|
||||
// we have sorted and we might have broken key orders
|
||||
// we want the first one (by its initial index)
|
||||
if ($notExactMatch) {
|
||||
$rowNumber = $rowKey;
|
||||
} elseif (($cellDataLower == $lookupLower) && (($rowNumber === null) || ($rowKey < $rowNumber))) {
|
||||
$rowNumber = $rowKey;
|
||||
}
|
||||
}
|
||||
|
||||
return $rowNumber;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
class Matrix
|
||||
{
|
||||
/**
|
||||
* TRANSPOSE.
|
||||
*
|
||||
* @param array $matrixData A matrix of values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function transpose($matrixData)
|
||||
{
|
||||
$returnMatrix = [];
|
||||
if (!is_array($matrixData)) {
|
||||
$matrixData = [[$matrixData]];
|
||||
}
|
||||
|
||||
$column = 0;
|
||||
foreach ($matrixData as $matrixRow) {
|
||||
$row = 0;
|
||||
foreach ($matrixRow as $matrixCell) {
|
||||
$returnMatrix[$row][$column] = $matrixCell;
|
||||
++$row;
|
||||
}
|
||||
++$column;
|
||||
}
|
||||
|
||||
return $returnMatrix;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class RowColumnInformation
|
||||
{
|
||||
/**
|
||||
* COLUMN.
|
||||
*
|
||||
* Returns the column number of the given cell reference
|
||||
* If the cell reference is a range of cells, COLUMN returns the column numbers of each column
|
||||
* in the reference as a horizontal array.
|
||||
* If cell reference is omitted, and the function is being called through the calculation engine,
|
||||
* then it is assumed to be the reference of the cell in which the COLUMN function appears;
|
||||
* otherwise this function returns 1.
|
||||
*
|
||||
* Excel Function:
|
||||
* =COLUMN([cellAddress])
|
||||
*
|
||||
* @param null|array|string $cellAddress A reference to a range of cells for which you want the column numbers
|
||||
*
|
||||
* @return int|int[]
|
||||
*/
|
||||
public static function COLUMN($cellAddress = null, ?Cell $cell = null)
|
||||
{
|
||||
if ($cellAddress === null || (!is_array($cellAddress) && trim($cellAddress) === '')) {
|
||||
return ($cell !== null) ? (int) Coordinate::columnIndexFromString($cell->getColumn()) : 1;
|
||||
}
|
||||
|
||||
if (is_array($cellAddress)) {
|
||||
foreach ($cellAddress as $columnKey => $value) {
|
||||
$columnKey = preg_replace('/[^a-z]/i', '', $columnKey);
|
||||
|
||||
return (int) Coordinate::columnIndexFromString($columnKey);
|
||||
}
|
||||
} else {
|
||||
[, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||
if (strpos($cellAddress, ':') !== false) {
|
||||
[$startAddress, $endAddress] = explode(':', $cellAddress);
|
||||
$startAddress = preg_replace('/[^a-z]/i', '', $startAddress);
|
||||
$endAddress = preg_replace('/[^a-z]/i', '', $endAddress);
|
||||
$returnValue = [];
|
||||
do {
|
||||
$returnValue[] = (int) Coordinate::columnIndexFromString($startAddress);
|
||||
} while ($startAddress++ != $endAddress);
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
$cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress);
|
||||
|
||||
return (int) Coordinate::columnIndexFromString($cellAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* COLUMNS.
|
||||
*
|
||||
* Returns the number of columns in an array or reference.
|
||||
*
|
||||
* Excel Function:
|
||||
* =COLUMNS(cellAddress)
|
||||
*
|
||||
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells
|
||||
* for which you want the number of columns
|
||||
*
|
||||
* @return int|string The number of columns in cellAddress, or a string if arguments are invalid
|
||||
*/
|
||||
public static function COLUMNS($cellAddress = null)
|
||||
{
|
||||
if ($cellAddress === null || $cellAddress === '') {
|
||||
return 1;
|
||||
} elseif (!is_array($cellAddress)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
reset($cellAddress);
|
||||
$isMatrix = (is_numeric(key($cellAddress)));
|
||||
[$columns, $rows] = Calculation::getMatrixDimensions($cellAddress);
|
||||
|
||||
if ($isMatrix) {
|
||||
return $rows;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* ROW.
|
||||
*
|
||||
* Returns the row number of the given cell reference
|
||||
* If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference
|
||||
* as a vertical array.
|
||||
* If cell reference is omitted, and the function is being called through the calculation engine,
|
||||
* then it is assumed to be the reference of the cell in which the ROW function appears;
|
||||
* otherwise this function returns 1.
|
||||
*
|
||||
* Excel Function:
|
||||
* =ROW([cellAddress])
|
||||
*
|
||||
* @param null|array|string $cellAddress A reference to a range of cells for which you want the row numbers
|
||||
*
|
||||
* @return int|mixed[]|string
|
||||
*/
|
||||
public static function ROW($cellAddress = null, ?Cell $pCell = null)
|
||||
{
|
||||
if ($cellAddress === null || (!is_array($cellAddress) && trim($cellAddress) === '')) {
|
||||
return ($pCell !== null) ? $pCell->getRow() : 1;
|
||||
}
|
||||
|
||||
if (is_array($cellAddress)) {
|
||||
foreach ($cellAddress as $columnKey => $rowValue) {
|
||||
foreach ($rowValue as $rowKey => $cellValue) {
|
||||
return (int) preg_replace('/\D/', '', $rowKey);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
[, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||
if (strpos($cellAddress, ':') !== false) {
|
||||
[$startAddress, $endAddress] = explode(':', $cellAddress);
|
||||
$startAddress = preg_replace('/\D/', '', $startAddress);
|
||||
$endAddress = preg_replace('/\D/', '', $endAddress);
|
||||
$returnValue = [];
|
||||
do {
|
||||
$returnValue[][] = (int) $startAddress;
|
||||
} while ($startAddress++ != $endAddress);
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
[$cellAddress] = explode(':', $cellAddress);
|
||||
|
||||
return (int) preg_replace('/\D/', '', $cellAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ROWS.
|
||||
*
|
||||
* Returns the number of rows in an array or reference.
|
||||
*
|
||||
* Excel Function:
|
||||
* =ROWS(cellAddress)
|
||||
*
|
||||
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells
|
||||
* for which you want the number of rows
|
||||
*
|
||||
* @return int|string The number of rows in cellAddress, or a string if arguments are invalid
|
||||
*/
|
||||
public static function ROWS($cellAddress = null)
|
||||
{
|
||||
if ($cellAddress === null || $cellAddress === '') {
|
||||
return 1;
|
||||
} elseif (!is_array($cellAddress)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
reset($cellAddress);
|
||||
$isMatrix = (is_numeric(key($cellAddress)));
|
||||
[$columns, $rows] = Calculation::getMatrixDimensions($cellAddress);
|
||||
|
||||
if ($isMatrix) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class VLookup extends LookupBase
|
||||
{
|
||||
/**
|
||||
* VLOOKUP
|
||||
* The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value
|
||||
* in the same row based on the index_number.
|
||||
*
|
||||
* @param mixed $lookupValue The value that you want to match in lookup_array
|
||||
* @param mixed $lookupArray The range of cells being searched
|
||||
* @param mixed $indexNumber The column number in table_array from which the matching value must be returned.
|
||||
* The first column is 1.
|
||||
* @param mixed $notExactMatch determines if you are looking for an exact match based on lookup_value
|
||||
*
|
||||
* @return mixed The value of the found cell
|
||||
*/
|
||||
public static function lookup($lookupValue, $lookupArray, $indexNumber, $notExactMatch = true)
|
||||
{
|
||||
$lookupValue = Functions::flattenSingleValue($lookupValue);
|
||||
$indexNumber = Functions::flattenSingleValue($indexNumber);
|
||||
$notExactMatch = Functions::flattenSingleValue($notExactMatch);
|
||||
|
||||
try {
|
||||
$indexNumber = self::validateIndexLookup($lookupArray, $indexNumber);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$f = array_keys($lookupArray);
|
||||
$firstRow = array_pop($f);
|
||||
if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray[$firstRow]))) {
|
||||
return Functions::REF();
|
||||
}
|
||||
$columnKeys = array_keys($lookupArray[$firstRow]);
|
||||
$returnColumn = $columnKeys[--$indexNumber];
|
||||
$firstColumn = array_shift($columnKeys);
|
||||
|
||||
if (!$notExactMatch) {
|
||||
uasort($lookupArray, ['self', 'vlookupSort']);
|
||||
}
|
||||
|
||||
$rowNumber = self::vLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch);
|
||||
|
||||
if ($rowNumber !== null) {
|
||||
// return the appropriate value
|
||||
return $lookupArray[$rowNumber][$returnColumn];
|
||||
}
|
||||
|
||||
return Functions::NA();
|
||||
}
|
||||
|
||||
private static function vlookupSort($a, $b)
|
||||
{
|
||||
reset($a);
|
||||
$firstColumn = key($a);
|
||||
$aLower = StringHelper::strToLower($a[$firstColumn]);
|
||||
$bLower = StringHelper::strToLower($b[$firstColumn]);
|
||||
|
||||
if ($aLower == $bLower) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($aLower < $bLower) ? -1 : 1;
|
||||
}
|
||||
|
||||
private static function vLookupSearch($lookupValue, $lookupArray, $column, $notExactMatch)
|
||||
{
|
||||
$lookupLower = StringHelper::strToLower($lookupValue);
|
||||
|
||||
$rowNumber = null;
|
||||
foreach ($lookupArray as $rowKey => $rowData) {
|
||||
$bothNumeric = is_numeric($lookupValue) && is_numeric($rowData[$column]);
|
||||
$bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData[$column]);
|
||||
$cellDataLower = StringHelper::strToLower($rowData[$column]);
|
||||
|
||||
// break if we have passed possible keys
|
||||
if (
|
||||
$notExactMatch &&
|
||||
(($bothNumeric && ($rowData[$column] > $lookupValue)) ||
|
||||
($bothNotNumeric && ($cellDataLower > $lookupLower)))
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
$rowNumber = self::checkMatch(
|
||||
$bothNumeric,
|
||||
$bothNotNumeric,
|
||||
$notExactMatch,
|
||||
$rowKey,
|
||||
$cellDataLower,
|
||||
$lookupLower,
|
||||
$rowNumber
|
||||
);
|
||||
}
|
||||
|
||||
return $rowNumber;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AddressTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerADDRESS
|
||||
*
|
||||
* @param mixed $expectedResult
|
||||
*/
|
||||
public function testADDRESS($expectedResult, ...$args): void
|
||||
{
|
||||
$result = LookupRef::cellAddress(...$args);
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function providerADDRESS()
|
||||
{
|
||||
return require 'tests/data/Calculation/LookupRef/ADDRESS.php';
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
|||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ColumnTest extends TestCase
|
||||
|
|
@ -17,8 +18,9 @@ class ColumnTest extends TestCase
|
|||
* @dataProvider providerCOLUMN
|
||||
*
|
||||
* @param mixed $expectedResult
|
||||
* @param null|mixed $cellReference
|
||||
*/
|
||||
public function testCOLUMN($expectedResult, string $cellReference): void
|
||||
public function testCOLUMN($expectedResult, $cellReference = null): void
|
||||
{
|
||||
$result = LookupRef::COLUMN($cellReference);
|
||||
self::assertSame($expectedResult, $result);
|
||||
|
|
@ -28,4 +30,17 @@ class ColumnTest extends TestCase
|
|||
{
|
||||
return require 'tests/data/Calculation/LookupRef/COLUMN.php';
|
||||
}
|
||||
|
||||
public function testCOLUMNwithNull(): void
|
||||
{
|
||||
$cell = $this->getMockBuilder(Cell::class)
|
||||
->setMethods(['getColumn'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$cell->method('getColumn')
|
||||
->willReturn('D');
|
||||
|
||||
$result = LookupRef::COLUMN(null, $cell);
|
||||
self::assertSame(4, $result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
|||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class RowTest extends TestCase
|
||||
|
|
@ -17,8 +18,9 @@ class RowTest extends TestCase
|
|||
* @dataProvider providerROW
|
||||
*
|
||||
* @param mixed $expectedResult
|
||||
* @param null|mixed $cellReference
|
||||
*/
|
||||
public function testROW($expectedResult, string $cellReference): void
|
||||
public function testROW($expectedResult, $cellReference = null): void
|
||||
{
|
||||
$result = LookupRef::ROW($cellReference);
|
||||
self::assertSame($expectedResult, $result);
|
||||
|
|
@ -28,4 +30,17 @@ class RowTest extends TestCase
|
|||
{
|
||||
return require 'tests/data/Calculation/LookupRef/ROW.php';
|
||||
}
|
||||
|
||||
public function testROWwithNull(): void
|
||||
{
|
||||
$cell = $this->getMockBuilder(Cell::class)
|
||||
->setMethods(['getRow'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$cell->method('getRow')
|
||||
->willReturn(3);
|
||||
|
||||
$result = LookupRef::ROW(null, $cell);
|
||||
self::assertSame(3, $result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class TransposeTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerTRANSPOSE
|
||||
*
|
||||
* @param mixed $expectedResult
|
||||
* @param mixed $matrix
|
||||
*/
|
||||
public function testTRANSPOSE($expectedResult, $matrix): void
|
||||
{
|
||||
$result = LookupRef::TRANSPOSE($matrix);
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
public function providerTRANSPOSE()
|
||||
{
|
||||
return require 'tests/data/Calculation/LookupRef/TRANSPOSE.php';
|
||||
}
|
||||
}
|
||||
|
|
@ -65,10 +65,11 @@ class DefaultValueBinderTest extends TestCase
|
|||
* @dataProvider providerDataTypeForValue
|
||||
*
|
||||
* @param mixed $expectedResult
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function testDataTypeForValue($expectedResult, ...$args): void
|
||||
public function testDataTypeForValue($expectedResult, $value): void
|
||||
{
|
||||
$result = DefaultValueBinder::dataTypeForValue(...$args);
|
||||
$result = DefaultValueBinder::dataTypeForValue($value);
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
'$C$2',
|
||||
2,
|
||||
3,
|
||||
],
|
||||
[
|
||||
'C$2',
|
||||
2,
|
||||
3,
|
||||
2,
|
||||
],
|
||||
[
|
||||
'$C2',
|
||||
2,
|
||||
3,
|
||||
3,
|
||||
],
|
||||
[
|
||||
'R2C[3]',
|
||||
2,
|
||||
3,
|
||||
2,
|
||||
false,
|
||||
],
|
||||
[
|
||||
'R[2]C3',
|
||||
2,
|
||||
3,
|
||||
3,
|
||||
false,
|
||||
],
|
||||
[
|
||||
"'[Book1]Sheet1'!R2C3",
|
||||
2,
|
||||
3,
|
||||
1,
|
||||
false,
|
||||
'[Book1]Sheet1',
|
||||
],
|
||||
[
|
||||
"'EXCEL SHEET'!R2C3",
|
||||
2,
|
||||
3,
|
||||
1,
|
||||
false,
|
||||
'EXCEL SHEET',
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
-2,
|
||||
-2,
|
||||
],
|
||||
];
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
1,
|
||||
null,
|
||||
],
|
||||
[
|
||||
2,
|
||||
'B13',
|
||||
|
|
@ -9,4 +13,20 @@ return [
|
|||
[2, 3, 4],
|
||||
'B2:D2',
|
||||
],
|
||||
[
|
||||
2,
|
||||
['B' => 'B5', 'C' => 'C5', 'D' => 'D5'],
|
||||
],
|
||||
[
|
||||
[2, 3, 4],
|
||||
'B2:D3',
|
||||
],
|
||||
[
|
||||
[2, 3, 4],
|
||||
'Sheet1!B2:D2',
|
||||
],
|
||||
[
|
||||
[2, 3, 4],
|
||||
'"WorkSheet #1"!B2:D2',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -308,4 +308,24 @@ return [
|
|||
2,
|
||||
false,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
'B',
|
||||
[
|
||||
['Selection column', 'C', 'B', 'A'],
|
||||
['Value to retrieve', 3, 2, 1],
|
||||
],
|
||||
'Nan',
|
||||
false,
|
||||
],
|
||||
[
|
||||
'#REF!',
|
||||
'B',
|
||||
[
|
||||
'Selection column',
|
||||
'Value to retrieve',
|
||||
],
|
||||
2,
|
||||
false,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
[ // Office reference example #1
|
||||
'orange',
|
||||
4.19,
|
||||
|
|
@ -74,7 +73,6 @@ return [
|
|||
['blue'],
|
||||
],
|
||||
],
|
||||
|
||||
[ // Array form test
|
||||
'orange',
|
||||
4.2,
|
||||
|
|
@ -86,7 +84,6 @@ return [
|
|||
[6, 39, 'blue'],
|
||||
],
|
||||
],
|
||||
|
||||
[
|
||||
5,
|
||||
'x',
|
||||
|
|
@ -95,20 +92,40 @@ return [
|
|||
[1, 2, 3, 4, 5],
|
||||
],
|
||||
],
|
||||
|
||||
[
|
||||
'author_100',
|
||||
100,
|
||||
'author_101',
|
||||
101,
|
||||
[
|
||||
[100],
|
||||
[101],
|
||||
[102],
|
||||
],
|
||||
[
|
||||
['author_100'],
|
||||
['author_101'],
|
||||
['author_102'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'author_101',
|
||||
101,
|
||||
[
|
||||
[100],
|
||||
[101],
|
||||
[102],
|
||||
],
|
||||
[
|
||||
['author_100', 'author_101', 'author_102'],
|
||||
],
|
||||
],
|
||||
[
|
||||
101,
|
||||
101,
|
||||
[
|
||||
[100],
|
||||
[101],
|
||||
],
|
||||
],
|
||||
|
||||
[
|
||||
'#N/A',
|
||||
'10y2',
|
||||
|
|
@ -123,5 +140,14 @@ return [
|
|||
[10.0],
|
||||
],
|
||||
],
|
||||
|
||||
[
|
||||
'#N/A',
|
||||
'10y2',
|
||||
'Not an Array',
|
||||
[
|
||||
[2.0],
|
||||
[7.0],
|
||||
[10.0],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
1,
|
||||
null,
|
||||
],
|
||||
[
|
||||
10,
|
||||
'C10',
|
||||
|
|
@ -9,4 +13,16 @@ return [
|
|||
[[10], [11], [12]],
|
||||
'C10:C12',
|
||||
],
|
||||
[
|
||||
[[10], [11], [12]],
|
||||
'C10:D12',
|
||||
],
|
||||
[
|
||||
[[10], [11], [12]],
|
||||
'Sheet1!C10:C12',
|
||||
],
|
||||
[
|
||||
[[10], [11], [12]],
|
||||
'"WorkSheet #1"!C10:C12',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
[
|
||||
['Jan', 'Feb', 'Mar', 'Apr'],
|
||||
[100, 200, 150, 300],
|
||||
],
|
||||
[
|
||||
['Jan', 100],
|
||||
['Feb', 200],
|
||||
['Mar', 150],
|
||||
['Apr', 300],
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
['a', 'aa'],
|
||||
['b', 'ab'],
|
||||
['c', 'ac'],
|
||||
['d', 'ad'],
|
||||
],
|
||||
[
|
||||
['a', 'b', 'c', 'd'],
|
||||
['aa', 'ab', 'ac', 'ad'],
|
||||
],
|
||||
],
|
||||
[
|
||||
[[3.14]],
|
||||
3.14,
|
||||
],
|
||||
];
|
||||
|
|
@ -312,7 +312,6 @@ return [
|
|||
2,
|
||||
false,
|
||||
],
|
||||
|
||||
[
|
||||
'#N/A',
|
||||
'10y2',
|
||||
|
|
@ -323,5 +322,31 @@ return [
|
|||
],
|
||||
2.0,
|
||||
],
|
||||
|
||||
[
|
||||
'#VALUE!',
|
||||
'10y2',
|
||||
[
|
||||
['5y-1', 2.0],
|
||||
['10y1', 7.0],
|
||||
['10y2', 10.0],
|
||||
],
|
||||
'NaN',
|
||||
],
|
||||
[
|
||||
'#REF!',
|
||||
'10y2',
|
||||
[
|
||||
],
|
||||
2.0,
|
||||
],
|
||||
[
|
||||
'#REF!',
|
||||
'10y2',
|
||||
[
|
||||
2.0,
|
||||
7.0,
|
||||
10.0,
|
||||
],
|
||||
2.0,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in New Issue