validation-bugsnag-email

This commit is contained in:
RafficMohammed
2023-01-31 13:17:59 +05:30
parent 2ec836b447
commit 9dd3f53910
769 changed files with 20242 additions and 14060 deletions

View File

@@ -6,8 +6,6 @@ use PhpOffice\PhpSpreadsheet\Calculation\Engine\BranchPruner;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\CyclicReferenceStack;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\Logger;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\Operands;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Token\Stack;
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
@@ -4452,7 +4450,7 @@ class Calculation
$refSheet = $pCellParent;
if ($pCellParent !== null && $rangeSheetRef !== '' && $rangeSheetRef !== $pCellParent->getTitle()) {
$refSheet = $pCellParent->getParent()->getSheetByName($rangeSheetRef);
$refSheet = $pCellParent->getParentOrThrow()->getSheetByName($rangeSheetRef);
}
if (ctype_digit($val) && $val <= 1048576) {
@@ -4632,14 +4630,6 @@ class Calculation
return $operand;
}
private const NUMERIC_BINARY_OPERATIONS = [
'+' => 'plusEquals',
'-' => 'minusEquals',
'*' => 'arrayTimesEquals',
'/' => 'arrayRightDivide',
'^' => 'power',
];
/**
* @param mixed $tokens
* @param null|string $cellID
@@ -4679,7 +4669,7 @@ class Calculation
if (
(isset($storeValue) || $tokenData['reference'] === 'NULL')
&& (!$storeValueAsBool || ErrorValue::isError($storeValue) || ($storeValue === 'Pruned branch'))
&& (!$storeValueAsBool || Information\ErrorValue::isError($storeValue) || ($storeValue === 'Pruned branch'))
) {
// If branching value is not true, we don't need to compute
if (!isset($fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey])) {
@@ -4711,7 +4701,7 @@ class Calculation
if (
(isset($storeValue) || $tokenData['reference'] === 'NULL')
&& ($storeValueAsBool || ErrorValue::isError($storeValue) || ($storeValue === 'Pruned branch'))
&& ($storeValueAsBool || Information\ErrorValue::isError($storeValue) || ($storeValue === 'Pruned branch'))
) {
// If branching value is true, we don't need to compute
if (!isset($fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey])) {
@@ -4732,13 +4722,33 @@ class Calculation
}
if ($token instanceof Operands\StructuredReference) {
throw new Exception('Structured References are not currently supported');
// The next step is converting any structured reference to a cell value of range
// to a new $token value, which can then be processed in the following code.
}
if ($cell === null) {
return $this->raiseFormulaError('Structured References must exist in a Cell context');
}
// if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
if (!is_numeric($token) && !is_object($token) && isset(self::BINARY_OPERATORS[$token])) {
try {
$cellRange = $token->parse($cell);
if (strpos($cellRange, ':') !== false) {
$this->debugLog->writeDebugLog('Evaluating Structured Reference %s as Cell Range %s', $token->value(), $cellRange);
$rangeValue = self::getInstance($cell->getWorksheet()->getParent())->_calculateFormulaValue("={$cellRange}", $token->value(), $cell);
$stack->push('Value', $rangeValue);
$this->debugLog->writeDebugLog('Evaluated Structured Reference %s as value %s', $token->value(), $this->showValue($rangeValue));
} else {
$this->debugLog->writeDebugLog('Evaluating Structured Reference %s as Cell %s', $token->value(), $cellRange);
$cellValue = $cell->getWorksheet()->getCell($cellRange)->getCalculatedValue(false);
$stack->push('Cell Reference', $cellValue, $cellRange);
$this->debugLog->writeDebugLog('Evaluated Structured Reference %s as value %s', $token->value(), $this->showValue($cellValue));
}
} catch (Exception $e) {
if ($e->getCode() === Exception::CALCULATION_ENGINE_PUSH_TO_STACK) {
$stack->push('Error', Information\ExcelError::REF(), null);
$this->debugLog->writeDebugLog('Evaluated Structured Reference %s as error value %s', $token->value(), Information\ExcelError::REF());
} else {
return $this->raiseFormulaError($e->getMessage());
}
}
} elseif (!is_numeric($token) && !is_object($token) && isset(self::BINARY_OPERATORS[$token])) {
// if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
// We must have two operands, error if we don't
if (($operand2Data = $stack->pop()) === null) {
return $this->raiseFormulaError('Internal error - Operand value missing from stack');
@@ -4842,7 +4852,7 @@ class Calculation
case '*': // Multiplication
case '/': // Division
case '^': // Exponential
$result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, self::NUMERIC_BINARY_OPERATIONS[$token], $stack);
$result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, $stack);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
@@ -4852,29 +4862,30 @@ class Calculation
// If either of the operands is a matrix, we need to treat them both as matrices
// (converting the other operand to a matrix if need be); then perform the required
// matrix operation
if (is_bool($operand1)) {
$operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
}
if (is_bool($operand2)) {
$operand2 = ($operand2) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
}
if ((is_array($operand1)) || (is_array($operand2))) {
// Ensure that both operands are arrays/matrices
self::checkMatrixOperands($operand1, $operand2, 2);
try {
// Convert operand 1 from a PHP array to a matrix
$matrix = new Shared\JAMA\Matrix($operand1);
// Perform the required operation against the operand 1 matrix, passing in operand 2
$matrixResult = $matrix->concat($operand2);
$result = $matrixResult->getArray();
if (isset($result[0][0])) {
$result[0][0] = Shared\StringHelper::substring($result[0][0], 0, DataType::MAX_STRING_LENGTH);
}
} catch (\Exception $ex) {
$this->debugLog->writeDebugLog('JAMA Matrix Exception: %s', $ex->getMessage());
$result = '#VALUE!';
$operand1 = self::boolToString($operand1);
$operand2 = self::boolToString($operand2);
if (is_array($operand1) || is_array($operand2)) {
if (is_string($operand1)) {
$operand1 = self::unwrapResult($operand1);
}
if (is_string($operand2)) {
$operand2 = self::unwrapResult($operand2);
}
// Ensure that both operands are arrays/matrices
[$rows, $columns] = self::checkMatrixOperands($operand1, $operand2, 2);
for ($row = 0; $row < $rows; ++$row) {
for ($column = 0; $column < $columns; ++$column) {
$operand1[$row][$column] =
Shared\StringHelper::substring(
self::boolToString($operand1[$row][$column])
. self::boolToString($operand2[$row][$column]),
0,
DataType::MAX_STRING_LENGTH
);
}
}
$result = $operand1;
} else {
// In theory, we should truncate here.
// But I can't figure out a formula
@@ -4927,23 +4938,26 @@ class Calculation
$multiplier = 0.01;
}
if (is_array($arg)) {
self::checkMatrixOperands($arg, $multiplier, 2);
try {
$matrix1 = new Shared\JAMA\Matrix($arg);
$matrixResult = $matrix1->arrayTimesEquals($multiplier);
$result = $matrixResult->getArray();
} catch (\Exception $ex) {
$this->debugLog->writeDebugLog('JAMA Matrix Exception: %s', $ex->getMessage());
$result = '#VALUE!';
$operand2 = $multiplier;
$result = $arg;
[$rows, $columns] = self::checkMatrixOperands($result, $operand2, 0);
for ($row = 0; $row < $rows; ++$row) {
for ($column = 0; $column < $columns; ++$column) {
if (is_numeric($result[$row][$column])) {
$result[$row][$column] *= $multiplier;
} else {
$result[$row][$column] = Information\ExcelError::VALUE();
}
}
}
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result));
$stack->push('Value', $result);
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
} else {
$this->executeNumericBinaryOperation($multiplier, $arg, '*', 'arrayTimesEquals', $stack);
$this->executeNumericBinaryOperation($multiplier, $arg, '*', $stack);
}
} elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token ?? '', $matches)) {
$cellRef = null;
@@ -5283,12 +5297,11 @@ class Calculation
* @param mixed $operand1
* @param mixed $operand2
* @param string $operation
* @param string $matrixFunction
* @param Stack $stack
*
* @return bool|mixed
*/
private function executeNumericBinaryOperation($operand1, $operand2, $operation, $matrixFunction, &$stack)
private function executeNumericBinaryOperation($operand1, $operand2, $operation, &$stack)
{
// Validate the two operands
if (
@@ -5298,70 +5311,113 @@ class Calculation
return false;
}
// If either of the operands is a matrix, we need to treat them both as matrices
// (converting the other operand to a matrix if need be); then perform the required
// matrix operation
if ((is_array($operand1)) || (is_array($operand2))) {
// Ensure that both operands are arrays/matrices of the same size
self::checkMatrixOperands($operand1, $operand2, 2);
try {
// Convert operand 1 from a PHP array to a matrix
$matrix = new Shared\JAMA\Matrix($operand1);
// Perform the required operation against the operand 1 matrix, passing in operand 2
$matrixResult = $matrix->$matrixFunction($operand2);
$result = $matrixResult->getArray();
} catch (\Exception $ex) {
$this->debugLog->writeDebugLog('JAMA Matrix Exception: %s', $ex->getMessage());
$result = '#VALUE!';
}
} else {
if (
(Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) &&
((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) ||
(is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0))
) {
$result = Information\ExcelError::VALUE();
} else {
// If we're dealing with non-matrix operations, execute the necessary operation
switch ($operation) {
// Addition
case '+':
$result = $operand1 + $operand2;
break;
// Subtraction
case '-':
$result = $operand1 - $operand2;
break;
// Multiplication
case '*':
$result = $operand1 * $operand2;
break;
// Division
case '/':
if ($operand2 == 0) {
// Trap for Divide by Zero error
$stack->push('Error', ExcelError::DIV0());
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails(ExcelError::DIV0()));
return false;
}
$result = $operand1 / $operand2;
break;
// Power
case '^':
$result = $operand1 ** $operand2;
break;
default:
throw new Exception('Unsupported numeric binary operation');
if (
(Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) &&
((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) ||
(is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0))
) {
$result = Information\ExcelError::VALUE();
} elseif (is_array($operand1) || is_array($operand2)) {
// Ensure that both operands are arrays/matrices
if (is_array($operand1)) {
foreach ($operand1 as $key => $value) {
$operand1[$key] = Functions::flattenArray($value);
}
}
if (is_array($operand2)) {
foreach ($operand2 as $key => $value) {
$operand2[$key] = Functions::flattenArray($value);
}
}
[$rows, $columns] = self::checkMatrixOperands($operand1, $operand2, 2);
for ($row = 0; $row < $rows; ++$row) {
for ($column = 0; $column < $columns; ++$column) {
if ($operand1[$row][$column] === null) {
$operand1[$row][$column] = 0;
} elseif (!is_numeric($operand1[$row][$column])) {
$operand1[$row][$column] = Information\ExcelError::VALUE();
continue;
}
if ($operand2[$row][$column] === null) {
$operand2[$row][$column] = 0;
} elseif (!is_numeric($operand2[$row][$column])) {
$operand1[$row][$column] = Information\ExcelError::VALUE();
continue;
}
switch ($operation) {
case '+':
$operand1[$row][$column] += $operand2[$row][$column];
break;
case '-':
$operand1[$row][$column] -= $operand2[$row][$column];
break;
case '*':
$operand1[$row][$column] *= $operand2[$row][$column];
break;
case '/':
if ($operand2[$row][$column] == 0) {
$operand1[$row][$column] = Information\ExcelError::DIV0();
} else {
$operand1[$row][$column] /= $operand2[$row][$column];
}
break;
case '^':
$operand1[$row][$column] = $operand1[$row][$column] ** $operand2[$row][$column];
break;
default:
throw new Exception('Unsupported numeric binary operation');
}
}
}
$result = $operand1;
} else {
// If we're dealing with non-matrix operations, execute the necessary operation
switch ($operation) {
// Addition
case '+':
$result = $operand1 + $operand2;
break;
// Subtraction
case '-':
$result = $operand1 - $operand2;
break;
// Multiplication
case '*':
$result = $operand1 * $operand2;
break;
// Division
case '/':
if ($operand2 == 0) {
// Trap for Divide by Zero error
$stack->push('Error', Information\ExcelError::DIV0());
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails(Information\ExcelError::DIV0()));
return false;
}
$result = $operand1 / $operand2;
break;
// Power
case '^':
$result = $operand1 ** $operand2;
break;
default:
throw new Exception('Unsupported numeric binary operation');
}
}
// Log the result details
@@ -5623,25 +5679,6 @@ class Calculation
return $args;
}
/**
* @param array $tokens
*
* @return string
*/
private function getTokensAsString($tokens)
{
$tokensStr = array_map(function ($token) {
$value = $token['value'] ?? 'no value';
while (is_array($value)) {
$value = array_pop($value);
}
return $value;
}, $tokens);
return '[ ' . implode(' | ', $tokensStr) . ' ]';
}
/**
* @return mixed|string
*/
@@ -5710,4 +5747,20 @@ class Calculation
{
return (bool) $arg;
}
/**
* @param mixed $operand1
*
* @return mixed
*/
private static function boolToString($operand1)
{
if (is_bool($operand1)) {
$operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
} elseif ($operand1 === null) {
$operand1 = '';
}
return $operand1;
}
}

View File

@@ -24,7 +24,7 @@ class DateValue
* Excel Function:
* DATEVALUE(dateValue)
*
* @param array|string $dateValue Text that represents a date in a Microsoft Excel date format.
* @param null|array|string $dateValue Text that represents a date in a Microsoft Excel date format.
* For example, "1/30/2008" or "30-Jan-2008" are text strings within
* quotation marks that represent dates. Using the default date
* system in Excel for Windows, date_text must represent a date from

View File

@@ -25,7 +25,7 @@ class TimeValue
* Excel Function:
* TIMEVALUE(timeValue)
*
* @param array|string $timeValue A text string that represents a time in any one of the Microsoft
* @param null|array|string $timeValue A text string that represents a time in any one of the Microsoft
* Excel time formats; for example, "6:45 PM" and "18:45" text strings
* within quotation marks that represent time.
* Date information in time_text is ignored.

View File

@@ -2,7 +2,11 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine\Operands;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
final class StructuredReference implements Operand
{
@@ -11,8 +15,39 @@ final class StructuredReference implements Operand
private const OPEN_BRACE = '[';
private const CLOSE_BRACE = ']';
private const ITEM_SPECIFIER_ALL = '#All';
private const ITEM_SPECIFIER_HEADERS = '#Headers';
private const ITEM_SPECIFIER_DATA = '#Data';
private const ITEM_SPECIFIER_TOTALS = '#Totals';
private const ITEM_SPECIFIER_THIS_ROW = '#This Row';
private const ITEM_SPECIFIER_ROWS_SET = [
self::ITEM_SPECIFIER_ALL,
self::ITEM_SPECIFIER_HEADERS,
self::ITEM_SPECIFIER_DATA,
self::ITEM_SPECIFIER_TOTALS,
];
private const TABLE_REFERENCE = '/([\p{L}_\\\\][\p{L}\p{N}\._]+)?(\[(?:[^\]\[]+|(?R))*+\])/miu';
private string $value;
private string $tableName;
private Table $table;
private string $reference;
private ?int $headersRow;
private int $firstDataRow;
private int $lastDataRow;
private ?int $totalsRow;
private array $columns;
public function __construct(string $structuredReference)
{
$this->value = $structuredReference;
@@ -42,8 +77,268 @@ final class StructuredReference implements Operand
return new self($val);
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
public function parse(Cell $cell): string
{
$this->getTableStructure($cell);
$cellRange = ($this->isRowReference()) ? $this->getRowReference($cell) : $this->getColumnReference();
return $cellRange;
}
private function isRowReference(): bool
{
return strpos($this->value, '[@') !== false
|| strpos($this->value, '[' . self::ITEM_SPECIFIER_THIS_ROW . ']') !== false;
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
private function getTableStructure(Cell $cell): void
{
preg_match(self::TABLE_REFERENCE, $this->value, $matches);
$this->tableName = $matches[1];
$this->table = ($this->tableName === '')
? $this->getTableForCell($cell)
: $this->getTableByName($cell);
$this->reference = $matches[2];
$tableRange = Coordinate::getRangeBoundaries($this->table->getRange());
$this->headersRow = ($this->table->getShowHeaderRow()) ? (int) $tableRange[0][1] : null;
$this->firstDataRow = ($this->table->getShowHeaderRow()) ? (int) $tableRange[0][1] + 1 : $tableRange[0][1];
$this->totalsRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] : null;
$this->lastDataRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] - 1 : $tableRange[1][1];
$this->columns = $this->getColumns($cell, $tableRange);
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
private function getTableForCell(Cell $cell): Table
{
$tables = $cell->getWorksheet()->getTableCollection();
foreach ($tables as $table) {
/** @var Table $table */
$range = $table->getRange();
if ($cell->isInRange($range) === true) {
$this->tableName = $table->getName();
return $table;
}
}
throw new Exception('Table for Structured Reference cannot be identified');
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
private function getTableByName(Cell $cell): Table
{
$table = $cell->getWorksheet()->getTableByName($this->tableName);
if ($table === null) {
throw new Exception("Table {$this->tableName} for Structured Reference cannot be located");
}
return $table;
}
private function getColumns(Cell $cell, array $tableRange): array
{
$worksheet = $cell->getWorksheet();
$cellReference = $cell->getCoordinate();
$columns = [];
$lastColumn = ++$tableRange[1][0];
for ($column = $tableRange[0][0]; $column !== $lastColumn; ++$column) {
$columns[$column] = $worksheet
->getCell($column . ($this->headersRow ?? ($this->firstDataRow - 1)))
->getCalculatedValue();
}
$cell = $worksheet->getCell($cellReference);
return $columns;
}
private function getRowReference(Cell $cell): string
{
$reference = str_replace("\u{a0}", ' ', $this->reference);
/** @var string $reference */
$reference = str_replace('[' . self::ITEM_SPECIFIER_THIS_ROW . '],', '', $reference);
foreach ($this->columns as $columnId => $columnName) {
$columnName = str_replace("\u{a0}", ' ', $columnName);
$reference = $this->adjustRowReference($columnName, $reference, $cell, $columnId);
}
/** @var string $reference */
return $this->validateParsedReference(trim($reference, '[]@, '));
}
private function adjustRowReference(string $columnName, string $reference, Cell $cell, string $columnId): string
{
if ($columnName !== '') {
$cellReference = $columnId . $cell->getRow();
$pattern1 = '/\[' . preg_quote($columnName) . '\]/miu';
$pattern2 = '/@' . preg_quote($columnName) . '/miu';
if (preg_match($pattern1, $reference) === 1) {
$reference = preg_replace($pattern1, $cellReference, $reference);
} elseif (preg_match($pattern2, $reference) === 1) {
$reference = preg_replace($pattern2, $cellReference, $reference);
}
/** @var string $reference */
}
return $reference;
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
private function getColumnReference(): string
{
$reference = str_replace("\u{a0}", ' ', $this->reference);
$startRow = ($this->totalsRow === null) ? $this->lastDataRow : $this->totalsRow;
$endRow = ($this->headersRow === null) ? $this->firstDataRow : $this->headersRow;
[$startRow, $endRow] = $this->getRowsForColumnReference($reference, $startRow, $endRow);
$reference = $this->getColumnsForColumnReference($reference, $startRow, $endRow);
$reference = trim($reference, '[]@, ');
if (substr_count($reference, ':') > 1) {
$cells = explode(':', $reference);
$firstCell = array_shift($cells);
$lastCell = array_pop($cells);
$reference = "{$firstCell}:{$lastCell}";
}
return $this->validateParsedReference($reference);
}
/**
* @throws Exception
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
private function validateParsedReference(string $reference): string
{
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . ':' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $reference) !== 1) {
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $reference) !== 1) {
throw new Exception(
"Invalid Structured Reference {$this->reference} {$reference}",
Exception::CALCULATION_ENGINE_PUSH_TO_STACK
);
}
}
return $reference;
}
private function fullData(int $startRow, int $endRow): string
{
$columns = array_keys($this->columns);
$firstColumn = array_shift($columns);
$lastColumn = (empty($columns)) ? $firstColumn : array_pop($columns);
return "{$firstColumn}{$startRow}:{$lastColumn}{$endRow}";
}
private function getMinimumRow(string $reference): int
{
switch ($reference) {
case self::ITEM_SPECIFIER_ALL:
case self::ITEM_SPECIFIER_HEADERS:
return $this->headersRow ?? $this->firstDataRow;
case self::ITEM_SPECIFIER_DATA:
return $this->firstDataRow;
case self::ITEM_SPECIFIER_TOTALS:
return $this->totalsRow ?? $this->lastDataRow;
}
return $this->headersRow ?? $this->firstDataRow;
}
private function getMaximumRow(string $reference): int
{
switch ($reference) {
case self::ITEM_SPECIFIER_HEADERS:
return $this->headersRow ?? $this->firstDataRow;
case self::ITEM_SPECIFIER_DATA:
return $this->lastDataRow;
case self::ITEM_SPECIFIER_ALL:
case self::ITEM_SPECIFIER_TOTALS:
return $this->totalsRow ?? $this->lastDataRow;
}
return $this->totalsRow ?? $this->lastDataRow;
}
public function value(): string
{
return $this->value;
}
/**
* @return array<int, int>
*/
private function getRowsForColumnReference(string &$reference, int $startRow, int $endRow): array
{
$rowsSelected = false;
foreach (self::ITEM_SPECIFIER_ROWS_SET as $rowReference) {
$pattern = '/\[' . $rowReference . '\]/mui';
/** @var string $reference */
if (preg_match($pattern, $reference) === 1) {
if (($rowReference === self::ITEM_SPECIFIER_HEADERS) && ($this->table->getShowHeaderRow() === false)) {
throw new Exception(
'Table Headers are Hidden, and should not be Referenced',
Exception::CALCULATION_ENGINE_PUSH_TO_STACK
);
}
$rowsSelected = true;
$startRow = min($startRow, $this->getMinimumRow($rowReference));
$endRow = max($endRow, $this->getMaximumRow($rowReference));
$reference = preg_replace($pattern, '', $reference);
}
}
if ($rowsSelected === false) {
// If there isn't any Special Item Identifier specified, then the selection defaults to data rows only.
$startRow = $this->firstDataRow;
$endRow = $this->lastDataRow;
}
return [$startRow, $endRow];
}
private function getColumnsForColumnReference(string $reference, int $startRow, int $endRow): string
{
$columnsSelected = false;
foreach ($this->columns as $columnId => $columnName) {
$columnName = str_replace("\u{a0}", ' ', $columnName);
$cellFrom = "{$columnId}{$startRow}";
$cellTo = "{$columnId}{$endRow}";
$cellReference = ($cellFrom === $cellTo) ? $cellFrom : "{$cellFrom}:{$cellTo}";
$pattern = '/\[' . preg_quote($columnName) . '\]/mui';
if (preg_match($pattern, $reference) === 1) {
$columnsSelected = true;
$reference = preg_replace($pattern, $cellReference, $reference);
}
/** @var string $reference */
}
if ($columnsSelected === false) {
return $this->fullData($startRow, $endRow);
}
return $reference;
}
}

View File

@@ -133,7 +133,7 @@ class BesselJ
return self::besselj2b($ax, $ord, $x);
}
private static function besselj2a(float $ax, int $ord, float $x)
private static function besselj2a(float $ax, int $ord, float $x): float
{
$tox = 2.0 / $ax;
$bjm = self::besselJ0($ax);
@@ -148,7 +148,7 @@ class BesselJ
return ($x < 0.0 && ($ord % 2) == 1) ? -$ans : $ans;
}
private static function besselj2b(float $ax, int $ord, float $x)
private static function besselj2b(float $ax, int $ord, float $x): float
{
$tox = 2.0 / $ax;
$jsum = false;

View File

@@ -11,6 +11,7 @@ abstract class ConvertBase
{
use ArrayEnabled;
/** @param mixed $value */
protected static function validateValue($value): string
{
if (is_bool($value)) {
@@ -29,6 +30,7 @@ abstract class ConvertBase
return strtoupper((string) $value);
}
/** @param mixed $places */
protected static function validatePlaces($places = null): ?int
{
if ($places === null) {

View File

@@ -572,7 +572,7 @@ class ConvertUOM
return ($baseValue * self::$unitConversions[$fromCategory][$toUOM]) / $toMultiplier;
}
private static function getUOMDetails(string $uom)
private static function getUOMDetails(string $uom): array
{
if (isset(self::$conversionUnits[$uom])) {
$unitCategory = self::$conversionUnits[$uom]['Group'];
@@ -678,7 +678,7 @@ class ConvertUOM
return $value;
}
private static function resolveTemperatureSynonyms(string $uom)
private static function resolveTemperatureSynonyms(string $uom): string
{
switch ($uom) {
case 'fah':

View File

@@ -10,7 +10,7 @@ class Erf
{
use ArrayEnabled;
private static $twoSqrtPi = 1.128379167095512574;
private const TWO_SQRT_PI = 1.128379167095512574;
/**
* ERF.
@@ -77,11 +77,16 @@ class Erf
return self::ERF($limit);
}
//
// Private method to calculate the erf value
//
/**
* Method to calculate the erf value.
*
* @param float|int|string $value
*
* @return float
*/
public static function erfValue($value)
{
$value = (float) $value;
if (abs($value) > 2.2) {
return 1 - ErfC::ERFC($value);
}
@@ -100,6 +105,6 @@ class Erf
}
} while (abs($term / $sum) > Functions::PRECISION);
return self::$twoSqrtPi * $sum;
return self::TWO_SQRT_PI * $sum;
}
}

View File

@@ -43,13 +43,18 @@ class ErfC
return ExcelError::VALUE();
}
//
// Private method to calculate the erfc value
//
private static $oneSqrtPi = 0.564189583547756287;
private const ONE_SQRT_PI = 0.564189583547756287;
/**
* Method to calculate the erfc value.
*
* @param float|int|string $value
*
* @return float
*/
private static function erfcValue($value)
{
$value = (float) $value;
if (abs($value) < 2.2) {
return 1 - Erf::erfValue($value);
}
@@ -72,6 +77,6 @@ class ErfC
$q2 = $b / $d;
} while ((abs($q1 - $q2) / $q2) > Functions::PRECISION);
return self::$oneSqrtPi * exp(-$value * $value) * $q2;
return self::ONE_SQRT_PI * exp(-$value * $value) * $q2;
}
}

View File

@@ -6,6 +6,8 @@ use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class Exception extends PhpSpreadsheetException
{
public const CALCULATION_ENGINE_PUSH_TO_STACK = 1;
/**
* Error handler callback.
*

View File

@@ -12,8 +12,6 @@ use PhpOffice\PhpSpreadsheet\Calculation\Financial\TreasuryBill;
/**
* @deprecated 1.18.0
*
* @codeCoverageIgnore
*/
class Financial
{
@@ -543,7 +541,7 @@ class Financial
* date when the security is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param int $price The security's price per $100 face value
* @param mixed $price The security's price per $100 face value
* @param int $redemption The security's redemption value per $100 face value
* @param int $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
@@ -1004,7 +1002,7 @@ class Financial
* is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param int $discount The security's discount rate
* @param mixed $discount The security's discount rate
* @param int $redemption The security's redemption value per $100 face value
* @param int $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
@@ -1035,8 +1033,8 @@ class Financial
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $issue The security's issue date
* @param int $rate The security's interest rate at date of issue
* @param int $yield The security's annual yield
* @param mixed $rate The security's interest rate at date of issue
* @param mixed $yield The security's annual yield
* @param int $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
@@ -1216,7 +1214,7 @@ class Financial
* Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date.
* The maturity date is the date when the Treasury bill expires.
* @param int $discount The Treasury bill's discount rate
* @param mixed $discount The Treasury bill's discount rate
*
* @return float|string Result, or a string containing an error
*/
@@ -1239,7 +1237,7 @@ class Financial
* when the Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date.
* The maturity date is the date when the Treasury bill expires.
* @param int $discount The Treasury bill's discount rate
* @param mixed $discount The Treasury bill's discount rate
*
* @return float|string Result, or a string containing an error
*/
@@ -1262,7 +1260,7 @@ class Financial
* when the Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date.
* The maturity date is the date when the Treasury bill expires.
* @param int $price The Treasury bill's price per $100 face value
* @param mixed $price The Treasury bill's price per $100 face value
*
* @return float|mixed|string
*/
@@ -1342,7 +1340,7 @@ class Financial
* is traded to the buyer.
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param int $price The security's price per $100 face value
* @param mixed $price The security's price per $100 face value
* @param int $redemption The security's redemption value per $100 face value
* @param int $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
@@ -1373,8 +1371,8 @@ class Financial
* @param mixed $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $issue The security's issue date
* @param int $rate The security's interest rate at date of issue
* @param int $price The security's price per $100 face value
* @param mixed $rate The security's interest rate at date of issue
* @param mixed $price The security's price per $100 face value
* @param int $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual

View File

@@ -171,8 +171,9 @@ class Amortization
$yearFrac = $yearFracx;
if (
($basis == FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) &&
($yearFrac < 1) && (Functions::scalar(DateTimeExcel\Helpers::isLeapYear($purchasedYear)))
$basis == FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
&& $yearFrac < 1
&& DateTimeExcel\Helpers::isLeapYear(Functions::scalar($purchasedYear))
) {
$yearFrac *= 365 / 366;
}

View File

@@ -199,7 +199,8 @@ class Interest
return $close ? $rate : ExcelError::NAN();
}
private static function rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type)
/** @return float|string */
private static function rateNextGuess(float $rate, int $numberOfPeriods, float $payment, float $presentValue, float $futureValue, int $type)
{
if ($rate == 0.0) {
return ExcelError::NAN();

View File

@@ -6,8 +6,10 @@ use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstan
class InterestAndPrincipal
{
/** @var float */
protected $interest;
/** @var float */
protected $principal;
public function __construct(
@@ -24,7 +26,7 @@ class InterestAndPrincipal
$principal = 0.0;
for ($i = 1; $i <= $period; ++$i) {
$interest = ($type === FinancialConstants::PAYMENT_BEGINNING_OF_PERIOD && $i == 1) ? 0 : -$capital * $rate;
$principal = $payment - $interest;
$principal = (float) $payment - $interest;
$capital += $principal;
}

View File

@@ -51,8 +51,11 @@ class NonPeriodic
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
$found = false;
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
if (!is_numeric($f1) || !is_numeric($f2)) {
break;
if (!is_numeric($f1)) {
return $f1;
}
if (!is_numeric($f2)) {
return $f2;
}
$f1 = (float) $f1;
$f2 = (float) $f2;
@@ -68,11 +71,32 @@ class NonPeriodic
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
}
}
if (!$found) {
return ExcelError::NAN();
if ($found) {
return self::xirrPart3($values, $dates, $x1, $x2);
}
return self::xirrPart3($values, $dates, $x1, $x2);
// Newton-Raphson didn't work - try bisection
$x1 = $guess - 0.5;
$x2 = $guess + 0.5;
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
$f1 = self::xnpvOrdered($x1, $values, $dates, false, true);
$f2 = self::xnpvOrdered($x2, $values, $dates, false, true);
if (!is_numeric($f1) || !is_numeric($f2)) {
break;
}
if ($f1 * $f2 <= 0) {
$found = true;
break;
}
$x1 -= 0.5;
$x2 += 0.5;
}
if ($found) {
return self::xirrBisection($values, $dates, $x1, $x2);
}
return ExcelError::NAN();
}
/**
@@ -190,6 +214,45 @@ class NonPeriodic
return $rslt;
}
/**
* @return float|string
*/
private static function xirrBisection(array $values, array $dates, float $x1, float $x2)
{
$rslt = ExcelError::NAN();
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
$rslt = ExcelError::NAN();
$f1 = self::xnpvOrdered($x1, $values, $dates, false, true);
$f2 = self::xnpvOrdered($x2, $values, $dates, false, true);
if (!is_numeric($f1) || !is_numeric($f2)) {
break;
}
$f1 = (float) $f1;
$f2 = (float) $f2;
if (abs($f1) < self::FINANCIAL_PRECISION && abs($f2) < self::FINANCIAL_PRECISION) {
break;
}
if ($f1 * $f2 > 0) {
break;
}
$rslt = ($x1 + $x2) / 2;
$f3 = self::xnpvOrdered($rslt, $values, $dates, false, true);
if (!is_float($f3)) {
break;
}
if ($f3 * $f1 < 0) {
$x2 = $rslt;
} else {
$x1 = $rslt;
}
if (abs($f3) < self::FINANCIAL_PRECISION) {
break;
}
}
return $rslt;
}
/**
* @param mixed $rate
* @param mixed $values
@@ -197,7 +260,7 @@ class NonPeriodic
*
* @return float|string
*/
private static function xnpvOrdered($rate, $values, $dates, bool $ordered = true)
private static function xnpvOrdered($rate, $values, $dates, bool $ordered = true, bool $capAtNegative1 = false)
{
$rate = Functions::flattenSingleValue($rate);
$values = Functions::flattenArray($values);
@@ -206,6 +269,9 @@ class NonPeriodic
try {
self::validateXnpv($rate, $values, $dates);
if ($capAtNegative1 && $rate <= -1) {
$rate = -1.0 + 1.0E-10;
}
$date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
} catch (Exception $e) {
return $e->getMessage();
@@ -225,7 +291,7 @@ class NonPeriodic
if ($date0 > $datei) {
$dif = $ordered ? ExcelError::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd'));
} else {
$dif = DateTimeExcel\Difference::interval($date0, $datei, 'd');
$dif = Functions::scalar(DateTimeExcel\Difference::interval($date0, $datei, 'd'));
}
if (!is_numeric($dif)) {
return $dif;

View File

@@ -144,6 +144,7 @@ class Periodic
* Returns the Net Present Value of a cash flow series given a discount rate.
*
* @param mixed $rate
* @param array $args
*
* @return float
*/

View File

@@ -200,7 +200,7 @@ class Coupons
}
/** @var int */
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis);
$daysPerYear = Helpers::daysPerYear(Functions::Scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
$next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_NASD) {

View File

@@ -208,6 +208,7 @@ class Depreciation
return $syd;
}
/** @param mixed $cost */
private static function validateCost($cost, bool $negativeValueAllowed = false): float
{
$cost = FinancialValidations::validateFloat($cost);
@@ -218,6 +219,7 @@ class Depreciation
return $cost;
}
/** @param mixed $salvage */
private static function validateSalvage($salvage, bool $negativeValueAllowed = false): float
{
$salvage = FinancialValidations::validateFloat($salvage);
@@ -228,6 +230,7 @@ class Depreciation
return $salvage;
}
/** @param mixed $life */
private static function validateLife($life, bool $negativeValueAllowed = false): float
{
$life = FinancialValidations::validateFloat($life);
@@ -238,6 +241,7 @@ class Depreciation
return $life;
}
/** @param mixed $period */
private static function validatePeriod($period, bool $negativeValueAllowed = false): float
{
$period = FinancialValidations::validateFloat($period);
@@ -248,6 +252,7 @@ class Depreciation
return $period;
}
/** @param mixed $month */
private static function validateMonth($month): int
{
$month = FinancialValidations::validateInt($month);
@@ -258,6 +263,7 @@ class Depreciation
return $month;
}
/** @param mixed $factor */
private static function validateFactor($factor): float
{
$factor = FinancialValidations::validateFloat($factor);

View File

@@ -70,10 +70,10 @@ class Price
return $e->getMessage();
}
$dsc = Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis);
$e = Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis);
$n = Coupons::COUPNUM($settlement, $maturity, $frequency, $basis);
$a = Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis);
$dsc = (float) Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis);
$e = (float) Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis);
$n = (int) Coupons::COUPNUM($settlement, $maturity, $frequency, $basis);
$a = (float) Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis);
$baseYF = 1.0 + ($yield / $frequency);
$rfp = 100 * ($rate / $frequency);
@@ -195,7 +195,7 @@ class Price
return $e->getMessage();
}
$daysPerYear = Functions::scalar(Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis));
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
@@ -276,7 +276,7 @@ class Price
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis);
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
return Functions::scalar($daysBetweenSettlementAndMaturity);
}
return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity));

View File

@@ -57,7 +57,7 @@ class Yields
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis);
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
@@ -122,7 +122,7 @@ class Yields
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis);
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}

View File

@@ -130,22 +130,26 @@ class Functions
return '#Not Yet Implemented';
}
public static function isMatrixValue($idx)
/** @param mixed $idx */
public static function isMatrixValue($idx): bool
{
return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0);
}
public static function isValue($idx)
/** @param mixed $idx */
public static function isValue($idx): bool
{
return substr_count($idx, '.') === 0;
}
public static function isCellValue($idx)
/** @param mixed $idx */
public static function isCellValue($idx): bool
{
return substr_count($idx, '.') > 1;
}
public static function ifCondition($condition)
/** @param mixed $condition */
public static function ifCondition($condition): string
{
$condition = self::flattenSingleValue($condition);
@@ -180,6 +184,11 @@ class Functions
return str_replace('""""', '""', $operator . $operand);
}
/**
* @param mixed $operand
*
* @return mixed
*/
private static function operandSpecialHandling($operand)
{
if (is_numeric($operand) || is_bool($operand)) {
@@ -635,7 +644,7 @@ class Functions
public static function expandDefinedName(string $coordinate, Cell $cell): string
{
$worksheet = $cell->getWorksheet();
$spreadsheet = $worksheet->getParent();
$spreadsheet = $worksheet->getParentOrThrow();
// Uppercase coordinate
$pCoordinatex = strtoupper($coordinate);
// Eliminate leading equal sign

View File

@@ -49,7 +49,7 @@ class Value
$cellValue = Functions::trimTrailingRange($value);
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/ui', $cellValue) === 1) {
[$worksheet, $cellValue] = Worksheet::extractSheetTitle($cellValue, true);
if (!empty($worksheet) && $cell->getWorksheet()->getParent()->getSheetByName($worksheet) === null) {
if (!empty($worksheet) && $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheet) === null) {
return false;
}
[$column, $row] = Coordinate::indexesFromString($cellValue);
@@ -60,7 +60,7 @@ class Value
return true;
}
$namedRange = $cell->getWorksheet()->getParent()->getNamedRange($value);
$namedRange = $cell->getWorksheet()->getParentOrThrow()->getNamedRange($value);
return $namedRange instanceof NamedRange;
}
@@ -227,7 +227,7 @@ class Value
$worksheetName = str_replace("''", "'", trim($matches[2], "'"));
$worksheet = (!empty($worksheetName))
? $cell->getWorksheet()->getParent()->getSheetByName($worksheetName)
? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheetName)
: $cell->getWorksheet();
return ($worksheet !== null) ? $worksheet->getCell($fullCellReference)->isFormula() : ExcelError::REF();

View File

@@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Internal;
class MakeMatrix
{
/** @param array $args */
public static function make(...$args): array
{
return $args;

View File

@@ -33,23 +33,9 @@ class Operations
*/
public static function logicalAnd(...$args)
{
$args = Functions::flattenArray($args);
if (count($args) == 0) {
return ExcelError::VALUE();
}
$args = array_filter($args, function ($value) {
return $value !== null || (is_string($value) && trim($value) == '');
return self::countTrueValues($args, function (int $trueValueCount, int $count): bool {
return $trueValueCount === $count;
});
$returnValue = self::countTrueValues($args);
if (is_string($returnValue)) {
return $returnValue;
}
$argCount = count($args);
return ($returnValue > 0) && ($returnValue == $argCount);
}
/**
@@ -74,22 +60,9 @@ class Operations
*/
public static function logicalOr(...$args)
{
$args = Functions::flattenArray($args);
if (count($args) == 0) {
return ExcelError::VALUE();
}
$args = array_filter($args, function ($value) {
return $value !== null || (is_string($value) && trim($value) == '');
return self::countTrueValues($args, function (int $trueValueCount): bool {
return $trueValueCount > 0;
});
$returnValue = self::countTrueValues($args);
if (is_string($returnValue)) {
return $returnValue;
}
return $returnValue > 0;
}
/**
@@ -116,22 +89,9 @@ class Operations
*/
public static function logicalXor(...$args)
{
$args = Functions::flattenArray($args);
if (count($args) == 0) {
return ExcelError::VALUE();
}
$args = array_filter($args, function ($value) {
return $value !== null || (is_string($value) && trim($value) == '');
return self::countTrueValues($args, function (int $trueValueCount): bool {
return $trueValueCount % 2 === 1;
});
$returnValue = self::countTrueValues($args);
if (is_string($returnValue)) {
return $returnValue;
}
return $returnValue % 2 == 1;
}
/**
@@ -177,31 +137,36 @@ class Operations
}
/**
* @return int|string
* @return bool|string
*/
private static function countTrueValues(array $args)
private static function countTrueValues(array $args, callable $func)
{
$trueValueCount = 0;
$count = 0;
foreach ($args as $arg) {
$aArgs = Functions::flattenArrayIndexed($args);
foreach ($aArgs as $k => $arg) {
++$count;
// Is it a boolean value?
if (is_bool($arg)) {
$trueValueCount += $arg;
} elseif ((is_numeric($arg)) && (!is_string($arg))) {
$trueValueCount += ((int) $arg != 0);
} elseif (is_string($arg)) {
$isLiteral = !Functions::isCellValue($k);
$arg = mb_strtoupper($arg, 'UTF-8');
if (($arg == 'TRUE') || ($arg == Calculation::getTRUE())) {
$arg = true;
} elseif (($arg == 'FALSE') || ($arg == Calculation::getFALSE())) {
$arg = false;
if ($isLiteral && ($arg == 'TRUE' || $arg == Calculation::getTRUE())) {
++$trueValueCount;
} elseif ($isLiteral && ($arg == 'FALSE' || $arg == Calculation::getFALSE())) {
//$trueValueCount += 0;
} else {
return ExcelError::VALUE();
--$count;
}
$trueValueCount += ($arg != 0);
} elseif (is_int($arg) || is_float($arg)) {
$trueValueCount += (int) ($arg != 0);
} else {
--$count;
}
}
return $trueValueCount;
return ($count === 0) ? ExcelError::VALUE() : $func($trueValueCount, $count);
}
}

View File

@@ -27,7 +27,7 @@ class Formula
$cellReference = $matches[6] . $matches[7];
$worksheetName = trim($matches[3], "'");
$worksheet = (!empty($worksheetName))
? $cell->getWorksheet()->getParent()->getSheetByName($worksheetName)
? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheetName)
: $cell->getWorksheet();
if (

View File

@@ -66,7 +66,7 @@ class Helpers
}
$worksheet = ($sheetName !== '')
? $cell->getWorksheet()->getParent()->getSheetByName($sheetName)
? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($sheetName)
: $cell->getWorksheet();
return [$cellAddress, $worksheet, $sheetName];

View File

@@ -108,7 +108,7 @@ class Offset
}
$worksheet = ($sheetName !== '')
? $cell->getWorksheet()->getParent()->getSheetByName($sheetName)
? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($sheetName)
: $cell->getWorksheet();
return [$cellAddress, $worksheet];

View File

@@ -766,7 +766,7 @@ class MathTrig
* @param string $criteria the criteria that defines which cells will be summed
* @param mixed $sumRange
*
* @return float|string
* @return null|float|string
*/
public static function SUMIF($range, $criteria, $sumRange = [])
{

View File

@@ -40,7 +40,7 @@ class Combinations
return $e->getMessage();
}
return round(Factorial::fact($numObjs) / Factorial::fact($numObjs - $numInSet)) / Factorial::fact($numInSet);
return round(Factorial::fact($numObjs) / Factorial::fact($numObjs - $numInSet)) / Factorial::fact($numInSet); // @phpstan-ignore-line
}
/**
@@ -85,7 +85,7 @@ class Combinations
}
return round(
Factorial::fact($numObjs + $numInSet - 1) / Factorial::fact($numObjs - 1)
Factorial::fact($numObjs + $numInSet - 1) / Factorial::fact($numObjs - 1) // @phpstan-ignore-line
) / Factorial::fact($numInSet);
}
}

View File

@@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
class Factorial
@@ -120,6 +121,6 @@ class Factorial
$summer = self::fact($summer);
return $summer / $divisor;
return is_numeric($summer) ? ($summer / $divisor) : ExcelError::VALUE();
}
}

View File

@@ -19,7 +19,7 @@ class IntClass
*
* @param array|float $number Number to cast to an integer, or can be an array of numbers
*
* @return array|string Integer value, or a string containing an error
* @return array|int|string Integer value, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -15,8 +15,6 @@ use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Variances;
/**
* @deprecated 1.18.0
*
* @codeCoverageIgnore
*/
class Statistical
{
@@ -953,7 +951,7 @@ class Statistical
*
* @param mixed $args Data range and criterias
*
* @return float
* @return null|float|string
*/
public static function MAXIFS(...$args)
{
@@ -1038,7 +1036,7 @@ class Statistical
*
* @param mixed $args Data range and criterias
*
* @return float
* @return null|float|string
*/
public static function MINIFS(...$args)
{
@@ -1728,7 +1726,7 @@ class Statistical
* Use the zTest() method in the Statistical\Distributions\StandardNormal class instead
* @see Statistical\Distributions\StandardNormal::zTest()
*
* @param float $dataSet
* @param mixed $dataSet
* @param float $m0 Alpha Parameter
* @param float $sigma Beta Parameter
*

View File

@@ -196,7 +196,7 @@ class Averages extends AggregateBase
return $returnValue;
}
protected static function filterArguments($args)
protected static function filterArguments(array $args): array
{
return array_filter(
$args,
@@ -207,11 +207,13 @@ class Averages extends AggregateBase
);
}
//
// Special variant of array_count_values that isn't limited to strings and integers,
// but can work with floating point numbers as values
//
private static function modeCalc($data)
/**
* Special variant of array_count_values that isn't limited to strings and integers,
* but can work with floating point numbers as values.
*
* @return float|string
*/
private static function modeCalc(array $data)
{
$frequencyArray = [];
$index = 0;

View File

@@ -187,7 +187,7 @@ class Conditional
* @param mixed $sumRange
* @param mixed $condition
*
* @return float|string
* @return null|float|string
*/
public static function SUMIF($range, $condition, $sumRange = [])
{
@@ -223,6 +223,7 @@ class Conditional
return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
}
/** @param array $args */
private static function buildConditionSet(...$args): array
{
$conditions = self::buildConditions(1, ...$args);
@@ -231,6 +232,7 @@ class Conditional
return array_map(/** @scrutinizer ignore-type */ null, ...$conditions);
}
/** @param array $args */
private static function buildConditionSetForValueRange(...$args): array
{
$conditions = self::buildConditions(2, ...$args);
@@ -247,6 +249,7 @@ class Conditional
return array_map(/** @scrutinizer ignore-type */ null, ...$conditions);
}
/** @param array $args */
private static function buildConditions(int $startOffset, ...$args): array
{
$conditions = [];
@@ -261,6 +264,7 @@ class Conditional
return $conditions;
}
/** @param array $args */
private static function buildDatabase(...$args): array
{
$database = [];
@@ -268,6 +272,7 @@ class Conditional
return self::buildDataSet(0, $database, ...$args);
}
/** @param array $args */
private static function buildDatabaseWithValueRange(...$args): array
{
$database = [];
@@ -279,6 +284,7 @@ class Conditional
return self::buildDataSet(1, $database, ...$args);
}
/** @param array $args */
private static function buildDataSet(int $startOffset, array $database, ...$args): array
{
$pairCount = 1;

View File

@@ -184,10 +184,13 @@ class Beta
}
// Function cache for logBeta function
/** @var float */
private static $logBetaCacheP = 0.0;
/** @var float */
private static $logBetaCacheQ = 0.0;
/** @var float */
private static $logBetaCacheResult = 0.0;
/**

View File

@@ -11,8 +11,6 @@ class ChiSquared
{
use ArrayEnabled;
private const MAX_ITERATIONS = 256;
private const EPS = 2.22e-16;
/**
@@ -197,7 +195,7 @@ class ChiSquared
$rows = count($actual);
$actual = Functions::flattenArray($actual);
$expected = Functions::flattenArray($expected);
$columns = count($actual) / $rows;
$columns = intdiv(count($actual), $rows);
$countActuals = count($actual);
$countExpected = count($expected);
@@ -261,12 +259,12 @@ class ChiSquared
return $chi2;
}
private static function pchisq($chi2, $degrees)
private static function pchisq(float $chi2, int $degrees): float
{
return self::gammp($degrees, 0.5 * $chi2);
}
private static function gammp($n, $x)
private static function gammp(int $n, float $x): float
{
if ($x < 0.5 * $n + 1) {
return self::gser($n, $x);
@@ -279,7 +277,7 @@ class ChiSquared
// series representation. Algorithm from numerical recipe.
// Assume that n is a positive integer and x>0, won't check arguments.
// Relative error controlled by the eps parameter
private static function gser($n, $x)
private static function gser(int $n, float $x): float
{
/** @var float */
$gln = Gamma::ln($n / 2);
@@ -303,7 +301,7 @@ class ChiSquared
// its continued fraction representation. Algorithm from numerical recipe.
// Assume that n is a postive integer and x>0, won't check arguments.
// Relative error controlled by the eps parameter
private static function gcf($n, $x)
private static function gcf(int $n, float $x): float
{
/** @var float */
$gln = Gamma::ln($n / 2);

View File

@@ -17,6 +17,7 @@ abstract class GammaBase
private const MAX_ITERATIONS = 256;
/** @return float|string */
protected static function calculateDistribution(float $value, float $a, float $b, bool $cumulative)
{
if ($cumulative) {
@@ -26,6 +27,7 @@ abstract class GammaBase
return (1 / ($b ** $a * self::gammaValue($a))) * $value ** ($a - 1) * exp(0 - ($value / $b));
}
/** @return float|string */
protected static function calculateInverse(float $probability, float $alpha, float $beta)
{
$xLo = 0;
@@ -38,6 +40,9 @@ abstract class GammaBase
while ((abs($dx) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) {
// Apply Newton-Raphson step
$result = self::calculateDistribution($x, $alpha, $beta, true);
if (!is_float($result)) {
return ExcelError::NA();
}
$error = $result - $probability;
if ($error == 0.0) {
@@ -50,6 +55,9 @@ abstract class GammaBase
$pdf = self::calculateDistribution($x, $alpha, $beta, false);
// Avoid division by zero
if (!is_float($pdf)) {
return ExcelError::NA();
}
if ($pdf !== 0.0) {
$dx = $error / $pdf;
$xNew = $x - $dx;
@@ -209,8 +217,10 @@ abstract class GammaBase
private const PNT68 = 0.6796875;
// Function cache for logGamma
/** @var float */
private static $logGammaCacheResult = 0.0;
/** @var float */
private static $logGammaCacheX = 0.0;
/**
@@ -292,7 +302,7 @@ abstract class GammaBase
return $res;
}
private static function logGamma1(float $y)
private static function logGamma1(float $y): float
{
// ---------------------
// EPS .LT. X .LE. 1.5
@@ -325,7 +335,7 @@ abstract class GammaBase
return $corr + $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden));
}
private static function logGamma2(float $y)
private static function logGamma2(float $y): float
{
// ---------------------
// 1.5 .LT. X .LE. 4.0
@@ -341,7 +351,7 @@ abstract class GammaBase
return $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden));
}
protected static function logGamma3(float $y)
protected static function logGamma3(float $y): float
{
// ----------------------
// 4.0 .LT. X .LE. 12.0
@@ -357,7 +367,7 @@ abstract class GammaBase
return self::LG_D4 + $xm4 * ($xnum / $xden);
}
protected static function logGamma4(float $y)
protected static function logGamma4(float $y): float
{
// ---------------------------------
// Evaluate for argument .GE. 12.0

View File

@@ -9,6 +9,7 @@ class NewtonRaphson
{
private const MAX_ITERATIONS = 256;
/** @var callable */
protected $callback;
public function __construct(callable $callback)
@@ -16,6 +17,7 @@ class NewtonRaphson
$this->callback = $callback;
}
/** @return float|string */
public function execute(float $probability)
{
$xLo = 100;

View File

@@ -104,7 +104,7 @@ class Normal
* email : nickersonm@yahoo.com
*
*/
private static function inverseNcdf($p)
private static function inverseNcdf(float $p): float
{
// Inverse ncdf approximation by Peter J. Acklam, implementation adapted to
// PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as

View File

@@ -146,6 +146,8 @@ class StandardNormal
}
$n = count($dataSet);
return 1 - self::cumulative((Averages::average($dataSet) - $m0) / ($sigma / sqrt($n)));
$sub1 = Averages::average($dataSet);
return is_numeric($sub1) ? (1 - self::cumulative(($sub1 - $m0) / ($sigma / sqrt($n)))) : $sub1;
}
}

View File

@@ -11,8 +11,6 @@ class StudentT
{
use ArrayEnabled;
private const MAX_ITERATIONS = 256;
/**
* TDIST.
*

View File

@@ -4,6 +4,11 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
abstract class MaxMinBase
{
/**
* @param mixed $value
*
* @return mixed
*/
protected static function datatypeAdjustmentAllowStrings($value)
{
if (is_bool($value)) {

View File

@@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
class Maximum extends MaxMinBase
{
@@ -26,6 +27,11 @@ class Maximum extends MaxMinBase
// Loop through arguments
$aArgs = Functions::flattenArray($args);
foreach ($aArgs as $arg) {
if (ErrorValue::isError($arg)) {
$returnValue = $arg;
break;
}
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
if (($returnValue === null) || ($arg > $returnValue)) {
@@ -60,6 +66,11 @@ class Maximum extends MaxMinBase
// Loop through arguments
$aArgs = Functions::flattenArray($args);
foreach ($aArgs as $arg) {
if (ErrorValue::isError($arg)) {
$returnValue = $arg;
break;
}
// Is it a numeric value?
if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
$arg = self::datatypeAdjustmentAllowStrings($arg);

View File

@@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
class Minimum extends MaxMinBase
{
@@ -26,6 +27,11 @@ class Minimum extends MaxMinBase
// Loop through arguments
$aArgs = Functions::flattenArray($args);
foreach ($aArgs as $arg) {
if (ErrorValue::isError($arg)) {
$returnValue = $arg;
break;
}
// Is it a numeric value?
if ((is_numeric($arg)) && (!is_string($arg))) {
if (($returnValue === null) || ($arg < $returnValue)) {
@@ -60,6 +66,11 @@ class Minimum extends MaxMinBase
// Loop through arguments
$aArgs = Functions::flattenArray($args);
foreach ($aArgs as $arg) {
if (ErrorValue::isError($arg)) {
$returnValue = $arg;
break;
}
// Is it a numeric value?
if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
$arg = self::datatypeAdjustmentAllowStrings($arg);

View File

@@ -110,7 +110,7 @@ class Percentiles
$pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos]));
}
return round($pos / $valueAdjustor, $significance);
return round(((float) $pos) / $valueAdjustor, $significance);
}
/**
@@ -184,7 +184,7 @@ class Percentiles
return ++$pos;
}
protected static function percentileFilterValues(array $dataSet)
protected static function percentileFilterValues(array $dataSet): array
{
return array_filter(
$dataSet,
@@ -194,7 +194,7 @@ class Percentiles
);
}
protected static function rankFilterValues(array $dataSet)
protected static function rankFilterValues(array $dataSet): array
{
return array_filter(
$dataSet,

View File

@@ -46,7 +46,16 @@ class Permutations
if ($numObjs < $numInSet) {
return ExcelError::NAN();
}
$result = round(MathTrig\Factorial::fact($numObjs) / MathTrig\Factorial::fact($numObjs - $numInSet));
$result1 = MathTrig\Factorial::fact($numObjs);
if (is_string($result1)) {
return $result1;
}
$result2 = MathTrig\Factorial::fact($numObjs - $numInSet);
if (is_string($result2)) {
return $result2;
}
// phpstan thinks result1 and result2 can be arrays; they can't.
$result = round($result1 / $result2); // @phpstan-ignore-line
return IntOrFloat::evaluate($result);
}

View File

@@ -6,6 +6,11 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions;
abstract class VarianceBase
{
/**
* @param mixed $value
*
* @return mixed
*/
protected static function datatypeAdjustmentAllowStrings($value)
{
if (is_bool($value)) {
@@ -17,6 +22,11 @@ abstract class VarianceBase
return $value;
}
/**
* @param mixed $value
*
* @return mixed
*/
protected static function datatypeAdjustmentBooleans($value)
{
if (is_bool($value) && (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) {

View File

@@ -8,6 +8,8 @@ interface AddressRange
public const MAX_COLUMN = 'XFD';
public const MAX_COLUMN_INT = 16384;
/**
* @return mixed
*/

View File

@@ -198,6 +198,7 @@ class Cell
*/
protected static function updateIfCellIsTableHeader(Worksheet $workSheet, self $cell, $oldValue, $newValue): void
{
// var_dump('=>', $oldValue, $newValue);
if (StringHelper::strToLower($oldValue ?? '') === StringHelper::strToLower($newValue ?? '')) {
return;
}
@@ -365,14 +366,14 @@ class Cell
{
if ($this->dataType === DataType::TYPE_FORMULA) {
try {
$index = $this->getWorksheet()->getParent()->getActiveSheetIndex();
$index = $this->getWorksheet()->getParentOrThrow()->getActiveSheetIndex();
$selected = $this->getWorksheet()->getSelectedCells();
$result = Calculation::getInstance(
$this->getWorksheet()->getParent()
)->calculateCellValue($this, $resetLog);
$result = $this->convertDateTimeInt($result);
$this->getWorksheet()->setSelectedCells($selected);
$this->getWorksheet()->getParent()->setActiveSheetIndex($index);
$this->getWorksheet()->getParentOrThrow()->setActiveSheetIndex($index);
// We don't yet handle array returns
if (is_array($result)) {
while (is_array($result)) {

View File

@@ -263,7 +263,7 @@ abstract class Coordinate
/**
* Column index from string.
*
* @param string $columnAddress eg 'A'
* @param ?string $columnAddress eg 'A'
*
* @return int Column index (A = 1)
*/
@@ -273,6 +273,7 @@ abstract class Coordinate
// caching using a static within the method is faster than a class static,
// though it's additional memory overhead
static $indexCache = [];
$columnAddress = $columnAddress ?? '';
if (isset($indexCache[$columnAddress])) {
return $indexCache[$columnAddress];

View File

@@ -116,8 +116,7 @@ class CellReferenceHelper
protected function updateColumnReference(int $newColumnIndex, string $absoluteColumn): string
{
$newColumn = Coordinate::stringFromColumnIndex($newColumnIndex + $this->numberOfColumns);
$newColumn = ($newColumn > AddressRange::MAX_COLUMN) ? AddressRange::MAX_COLUMN : $newColumn;
$newColumn = Coordinate::stringFromColumnIndex(min($newColumnIndex + $this->numberOfColumns, AddressRange::MAX_COLUMN_INT));
return $absoluteColumn . $newColumn;
}

View File

@@ -18,7 +18,7 @@ abstract class DefinedName
/**
* Worksheet on which the defined name can be resolved.
*
* @var Worksheet
* @var ?Worksheet
*/
protected $worksheet;
@@ -39,7 +39,7 @@ abstract class DefinedName
/**
* Scope.
*
* @var Worksheet
* @var ?Worksheet
*/
protected $scope;
@@ -141,17 +141,19 @@ abstract class DefinedName
// Re-attach
if ($this->worksheet !== null) {
$this->worksheet->getParent()->removeNamedRange($this->name, $this->worksheet);
$this->worksheet->getParentOrThrow()->removeNamedRange($this->name, $this->worksheet);
}
$this->name = $name;
if ($this->worksheet !== null) {
$this->worksheet->getParent()->addNamedRange($this);
$this->worksheet->getParentOrThrow()->addDefinedName($this);
}
// New title
$newTitle = $this->name;
ReferenceHelper::getInstance()->updateNamedFormulae($this->worksheet->getParent(), $oldTitle, $newTitle);
if ($this->worksheet !== null) {
// New title
$newTitle = $this->name;
ReferenceHelper::getInstance()->updateNamedFormulae($this->worksheet->getParentOrThrow(), $oldTitle, $newTitle);
}
}
return $this;
@@ -247,13 +249,13 @@ abstract class DefinedName
if ($sheetName === '') {
$worksheet2 = $worksheet;
} else {
$worksheet2 = $worksheet->getParent()->getSheetByName($sheetName);
$worksheet2 = $worksheet->getParentOrThrow()->getSheetByName($sheetName);
if ($worksheet2 === null) {
return null;
}
}
return $worksheet->getParent()->getDefinedName($definedName, $worksheet2);
return $worksheet->getParentOrThrow()->getDefinedName($definedName, $worksheet2);
}
/**

View File

@@ -88,7 +88,9 @@ abstract class IOFactory
* @param string $filename The name of the spreadsheet file
* @param int $flags the optional second parameter flags may be used to identify specific elements
* that should be loaded, but which won't be loaded by default, using these values:
* IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file
* IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file.
* IReader::READ_DATA_ONLY - Read cell values only, not formatting or merge structure.
* IReader::IGNORE_EMPTY_CELLS - Don't load empty cells into the model.
* @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try
* all possible Readers until it finds a match; but this allows you to pass in a
* list of Readers so it will only try the subset that you specify here.

View File

@@ -147,7 +147,7 @@ abstract class BaseReader implements IReader
if (((bool) ($flags & self::READ_DATA_ONLY)) === true) {
$this->setReadDataOnly(true);
}
if (((bool) ($flags & self::SKIP_EMPTY_CELLS)) === true) {
if (((bool) ($flags & self::SKIP_EMPTY_CELLS) || (bool) ($flags & self::IGNORE_EMPTY_CELLS)) === true) {
$this->setReadEmptyCells(false);
}
}

View File

@@ -9,6 +9,7 @@ interface IReader
public const READ_DATA_ONLY = 2;
public const SKIP_EMPTY_CELLS = 4;
public const IGNORE_EMPTY_CELLS = 4;
/**
* IReader constructor.
@@ -22,7 +23,8 @@ interface IReader
/**
* Read data only?
* If this is true, then the Reader will only read data values for cells, it will not read any formatting information.
* If this is true, then the Reader will only read data values for cells, it will not read any formatting
* or structural information (like merges).
* If false (the default) it will read data and formatting.
*
* @return bool
@@ -31,7 +33,8 @@ interface IReader
/**
* Set read data only
* Set to true, to advise the Reader only to read data values for cells, and to ignore any formatting information.
* Set to true, to advise the Reader only to read data values for cells, and to ignore any formatting
* or structural information (like merges).
* Set to false (the default) to advise the Reader to read both data and formatting for cells.
*
* @param bool $readDataOnly
@@ -62,9 +65,9 @@ interface IReader
/**
* Read charts in workbook?
* If this is true, then the Reader will include any charts that exist in the workbook.
* Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
* If false (the default) it will ignore any charts defined in the workbook file.
* If this is true, then the Reader will include any charts that exist in the workbook.
* Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
* If false (the default) it will ignore any charts defined in the workbook file.
*
* @return bool
*/
@@ -72,9 +75,9 @@ interface IReader
/**
* Set read charts in workbook
* Set to true, to advise the Reader to include any charts that exist in the workbook.
* Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
* Set to false (the default) to discard charts.
* Set to true, to advise the Reader to include any charts that exist in the workbook.
* Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
* Set to false (the default) to discard charts.
*
* @param bool $includeCharts
*

View File

@@ -3211,10 +3211,10 @@ class Xls extends BaseReader
for ($i = 0; $i < $nm; ++$i) {
$c = self::getUInt2d($recordData, 2 + 6 * $i);
$rf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
$rl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
//$rl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
// not sure why two row indexes are necessary?
$this->phpSheet->setBreak([$c + 1, $rf], Worksheet::BREAK_COLUMN);
$this->phpSheet->setBreak([$c + 1, ($rf > 0) ? $rf : 1], Worksheet::BREAK_COLUMN);
}
}
}

View File

@@ -122,9 +122,12 @@ class Xlsx extends BaseReader
return is_array($value) ? $value : [];
}
private function loadZip(string $filename, string $ns = ''): SimpleXMLElement
private function loadZip(string $filename, string $ns = '', bool $replaceUnclosedBr = false): SimpleXMLElement
{
$contents = $this->getFromZipArchive($this->zip, $filename);
if ($replaceUnclosedBr) {
$contents = str_replace('<br>', '<br/>', $contents);
}
$rels = simplexml_load_string(
$this->securityScanner->scan($contents),
'SimpleXMLElement',
@@ -1029,6 +1032,7 @@ class Xlsx extends BaseReader
// later we will remove from it real vmlComments
$unparsedVmlDrawings = $vmlComments;
$vmlDrawingContents = [];
// Loop through VML comments
foreach ($vmlComments as $relName => $relPath) {
@@ -1037,7 +1041,7 @@ class Xlsx extends BaseReader
try {
// no namespace okay - processed with Xpath
$vmlCommentsFile = $this->loadZip($relPath, '');
$vmlCommentsFile = $this->loadZip($relPath, '', true);
$vmlCommentsFile->registerXPathNamespace('v', Namespaces::URN_VML);
} catch (Throwable $ex) {
//Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData
@@ -1047,6 +1051,7 @@ class Xlsx extends BaseReader
// Locate VML drawings image relations
$drowingImages = [];
$VMLDrawingsRelations = dirname($relPath) . '/_rels/' . basename($relPath) . '.rels';
$vmlDrawingContents[$relName] = $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath));
if ($zip->locateName($VMLDrawingsRelations)) {
$relsVMLDrawing = $this->loadZip($VMLDrawingsRelations, Namespaces::RELATIONSHIPS);
foreach ($relsVMLDrawing->Relationship as $elex) {
@@ -1519,6 +1524,14 @@ class Xlsx extends BaseReader
}
}
}
if ($xmlSheet->legacyDrawing && !$this->readDataOnly) {
foreach ($xmlSheet->legacyDrawing as $drawing) {
$drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id');
if (isset($vmlDrawingContents[$drawingRelId])) {
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['legacyDrawing'] = $vmlDrawingContents[$drawingRelId];
}
}
}
// unparsed drawing AlternateContent
$xmlAltDrawing = $this->loadZip((string) $fileDrawing, Namespaces::COMPATIBILITY);

View File

@@ -550,7 +550,7 @@ class ReferenceHelper
}
// Update workbook: define names
if (count($worksheet->getParent()->getDefinedNames()) > 0) {
if (count($worksheet->getParentOrThrow()->getDefinedNames()) > 0) {
$this->updateDefinedNames($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
}
@@ -905,7 +905,7 @@ class ReferenceHelper
private function updateDefinedNames(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
{
foreach ($worksheet->getParent()->getDefinedNames() as $definedName) {
foreach ($worksheet->getParentOrThrow()->getDefinedNames() as $definedName) {
if ($definedName->isFormula() === false) {
$this->updateNamedRange($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
} else {

View File

@@ -1,16 +0,0 @@
Mar 1, 2005 11:15 AST by PM
+ For consistency, renamed Math.php to Maths.java, utils to util,
tests to test, docs to doc -
+ Removed conditional logic from top of Matrix class.
+ Switched to using hypo function in Maths.php for all php-hypot calls.
NOTE TO SELF: Need to make sure that all decompositions have been
switched over to using the bundled hypo.
Feb 25, 2005 at 10:00 AST by PM
+ Recommend using simpler Error.php instead of JAMA_Error.php but
can be persuaded otherwise.

View File

@@ -1,286 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
/**
* For an m-by-n matrix A with m >= n, the LU decomposition is an m-by-n
* unit lower triangular matrix L, an n-by-n upper triangular matrix U,
* and a permutation vector piv of length m so that A(piv,:) = L*U.
* If m < n, then L is m-by-m and U is m-by-n.
*
* The LU decompostion with pivoting always exists, even if the matrix is
* singular, so the constructor will never fail. The primary use of the
* LU decomposition is in the solution of square systems of simultaneous
* linear equations. This will fail if isNonsingular() returns false.
*
* @author Paul Meagher
* @author Bartosz Matosiuk
* @author Michael Bommarito
*
* @version 1.1
*/
class LUDecomposition
{
const MATRIX_SINGULAR_EXCEPTION = 'Can only perform operation on singular matrix.';
const MATRIX_SQUARE_EXCEPTION = 'Mismatched Row dimension';
/**
* Decomposition storage.
*
* @var array
*/
private $LU = [];
/**
* Row dimension.
*
* @var int
*/
private $m;
/**
* Column dimension.
*
* @var int
*/
private $n;
/**
* Pivot sign.
*
* @var int
*/
private $pivsign;
/**
* Internal storage of pivot vector.
*
* @var array
*/
private $piv = [];
/**
* LU Decomposition constructor.
*
* @param ?Matrix $A Rectangular matrix
*/
public function __construct($A)
{
if ($A instanceof Matrix) {
// Use a "left-looking", dot-product, Crout/Doolittle algorithm.
$this->LU = $A->getArray();
$this->m = $A->getRowDimension();
$this->n = $A->getColumnDimension();
for ($i = 0; $i < $this->m; ++$i) {
$this->piv[$i] = $i;
}
$this->pivsign = 1;
$LUcolj = [];
// Outer loop.
for ($j = 0; $j < $this->n; ++$j) {
// Make a copy of the j-th column to localize references.
for ($i = 0; $i < $this->m; ++$i) {
$LUcolj[$i] = &$this->LU[$i][$j];
}
// Apply previous transformations.
for ($i = 0; $i < $this->m; ++$i) {
$LUrowi = $this->LU[$i];
// Most of the time is spent in the following dot product.
$kmax = min($i, $j);
$s = 0.0;
for ($k = 0; $k < $kmax; ++$k) {
$s += $LUrowi[$k] * $LUcolj[$k];
}
$LUrowi[$j] = $LUcolj[$i] -= $s;
}
// Find pivot and exchange if necessary.
$p = $j;
for ($i = $j + 1; $i < $this->m; ++$i) {
if (abs($LUcolj[$i]) > abs($LUcolj[$p])) {
$p = $i;
}
}
if ($p != $j) {
for ($k = 0; $k < $this->n; ++$k) {
$t = $this->LU[$p][$k];
$this->LU[$p][$k] = $this->LU[$j][$k];
$this->LU[$j][$k] = $t;
}
$k = $this->piv[$p];
$this->piv[$p] = $this->piv[$j];
$this->piv[$j] = $k;
$this->pivsign = $this->pivsign * -1;
}
// Compute multipliers.
if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) {
for ($i = $j + 1; $i < $this->m; ++$i) {
$this->LU[$i][$j] /= $this->LU[$j][$j];
}
}
}
} else {
throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION);
}
}
// function __construct()
/**
* Get lower triangular factor.
*
* @return Matrix Lower triangular factor
*/
public function getL()
{
$L = [];
for ($i = 0; $i < $this->m; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
if ($i > $j) {
$L[$i][$j] = $this->LU[$i][$j];
} elseif ($i == $j) {
$L[$i][$j] = 1.0;
} else {
$L[$i][$j] = 0.0;
}
}
}
return new Matrix($L);
}
// function getL()
/**
* Get upper triangular factor.
*
* @return Matrix Upper triangular factor
*/
public function getU()
{
$U = [];
for ($i = 0; $i < $this->n; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
if ($i <= $j) {
$U[$i][$j] = $this->LU[$i][$j];
} else {
$U[$i][$j] = 0.0;
}
}
}
return new Matrix($U);
}
// function getU()
/**
* Return pivot permutation vector.
*
* @return array Pivot vector
*/
public function getPivot()
{
return $this->piv;
}
// function getPivot()
/**
* Alias for getPivot.
*
* @see getPivot
*
* @return array Pivot vector
*/
public function getDoublePivot()
{
return $this->getPivot();
}
// function getDoublePivot()
/**
* Is the matrix nonsingular?
*
* @return bool true if U, and hence A, is nonsingular
*/
public function isNonsingular()
{
for ($j = 0; $j < $this->n; ++$j) {
if ($this->LU[$j][$j] == 0) {
return false;
}
}
return true;
}
// function isNonsingular()
/**
* Count determinants.
*
* @return float
*/
public function det()
{
if ($this->m == $this->n) {
$d = $this->pivsign;
for ($j = 0; $j < $this->n; ++$j) {
$d *= $this->LU[$j][$j];
}
return $d;
}
throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
}
// function det()
/**
* Solve A*X = B.
*
* @param Matrix $B a Matrix with as many rows as A and any number of columns
*
* @return Matrix X so that L*U*X = B(piv,:)
*/
public function solve(Matrix $B)
{
if ($B->getRowDimension() == $this->m) {
if ($this->isNonsingular()) {
// Copy right hand side with pivoting
$nx = $B->getColumnDimension();
$X = $B->getMatrix($this->piv, 0, $nx - 1);
// Solve L*Y = B(piv,:)
for ($k = 0; $k < $this->n; ++$k) {
for ($i = $k + 1; $i < $this->n; ++$i) {
for ($j = 0; $j < $nx; ++$j) {
$X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k];
}
}
}
// Solve U*X = Y;
for ($k = $this->n - 1; $k >= 0; --$k) {
for ($j = 0; $j < $nx; ++$j) {
$X->A[$k][$j] /= $this->LU[$k][$k];
}
for ($i = 0; $i < $k; ++$i) {
for ($j = 0; $j < $nx; ++$j) {
$X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k];
}
}
}
return $X;
}
throw new CalculationException(self::MATRIX_SINGULAR_EXCEPTION);
}
throw new CalculationException(self::MATRIX_SQUARE_EXCEPTION);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,245 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
/**
* For an m-by-n matrix A with m >= n, the QR decomposition is an m-by-n
* orthogonal matrix Q and an n-by-n upper triangular matrix R so that
* A = Q*R.
*
* The QR decompostion always exists, even if the matrix does not have
* full rank, so the constructor will never fail. The primary use of the
* QR decomposition is in the least squares solution of nonsquare systems
* of simultaneous linear equations. This will fail if isFullRank()
* returns false.
*
* @author Paul Meagher
*
* @version 1.1
*/
class QRDecomposition
{
const MATRIX_RANK_EXCEPTION = 'Can only perform operation on full-rank matrix.';
/**
* Array for internal storage of decomposition.
*
* @var array
*/
private $QR = [];
/**
* Row dimension.
*
* @var int
*/
private $m;
/**
* Column dimension.
*
* @var int
*/
private $n;
/**
* Array for internal storage of diagonal of R.
*
* @var array
*/
private $Rdiag = [];
/**
* QR Decomposition computed by Householder reflections.
*
* @param Matrix $A Rectangular matrix
*/
public function __construct(Matrix $A)
{
// Initialize.
$this->QR = $A->getArray();
$this->m = $A->getRowDimension();
$this->n = $A->getColumnDimension();
// Main loop.
for ($k = 0; $k < $this->n; ++$k) {
// Compute 2-norm of k-th column without under/overflow.
$nrm = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$nrm = hypo($nrm, $this->QR[$i][$k]);
}
if ($nrm != 0.0) {
// Form k-th Householder vector.
if ($this->QR[$k][$k] < 0) {
$nrm = -$nrm;
}
for ($i = $k; $i < $this->m; ++$i) {
$this->QR[$i][$k] /= $nrm;
}
$this->QR[$k][$k] += 1.0;
// Apply transformation to remaining columns.
for ($j = $k + 1; $j < $this->n; ++$j) {
$s = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$s += $this->QR[$i][$k] * $this->QR[$i][$j];
}
$s = -$s / $this->QR[$k][$k];
for ($i = $k; $i < $this->m; ++$i) {
$this->QR[$i][$j] += $s * $this->QR[$i][$k];
}
}
}
$this->Rdiag[$k] = -$nrm;
}
}
// function __construct()
/**
* Is the matrix full rank?
*
* @return bool true if R, and hence A, has full rank, else false
*/
public function isFullRank()
{
for ($j = 0; $j < $this->n; ++$j) {
if ($this->Rdiag[$j] == 0) {
return false;
}
}
return true;
}
// function isFullRank()
/**
* Return the Householder vectors.
*
* @return Matrix Lower trapezoidal matrix whose columns define the reflections
*/
public function getH()
{
$H = [];
for ($i = 0; $i < $this->m; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
if ($i >= $j) {
$H[$i][$j] = $this->QR[$i][$j];
} else {
$H[$i][$j] = 0.0;
}
}
}
return new Matrix($H);
}
// function getH()
/**
* Return the upper triangular factor.
*
* @return Matrix upper triangular factor
*/
public function getR()
{
$R = [];
for ($i = 0; $i < $this->n; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
if ($i < $j) {
$R[$i][$j] = $this->QR[$i][$j];
} elseif ($i == $j) {
$R[$i][$j] = $this->Rdiag[$i];
} else {
$R[$i][$j] = 0.0;
}
}
}
return new Matrix($R);
}
// function getR()
/**
* Generate and return the (economy-sized) orthogonal factor.
*
* @return Matrix orthogonal factor
*/
public function getQ()
{
$Q = [];
for ($k = $this->n - 1; $k >= 0; --$k) {
for ($i = 0; $i < $this->m; ++$i) {
$Q[$i][$k] = 0.0;
}
$Q[$k][$k] = 1.0;
for ($j = $k; $j < $this->n; ++$j) {
if ($this->QR[$k][$k] != 0) {
$s = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$s += $this->QR[$i][$k] * $Q[$i][$j];
}
$s = -$s / $this->QR[$k][$k];
for ($i = $k; $i < $this->m; ++$i) {
$Q[$i][$j] += $s * $this->QR[$i][$k];
}
}
}
}
return new Matrix($Q);
}
// function getQ()
/**
* Least squares solution of A*X = B.
*
* @param Matrix $B a Matrix with as many rows as A and any number of columns
*
* @return Matrix matrix that minimizes the two norm of Q*R*X-B
*/
public function solve(Matrix $B)
{
if ($B->getRowDimension() == $this->m) {
if ($this->isFullRank()) {
// Copy right hand side
$nx = $B->getColumnDimension();
$X = $B->getArray();
// Compute Y = transpose(Q)*B
for ($k = 0; $k < $this->n; ++$k) {
for ($j = 0; $j < $nx; ++$j) {
$s = 0.0;
for ($i = $k; $i < $this->m; ++$i) {
$s += $this->QR[$i][$k] * $X[$i][$j];
}
$s = -$s / $this->QR[$k][$k];
for ($i = $k; $i < $this->m; ++$i) {
$X[$i][$j] += $s * $this->QR[$i][$k];
}
}
}
// Solve R*X = Y;
for ($k = $this->n - 1; $k >= 0; --$k) {
for ($j = 0; $j < $nx; ++$j) {
$X[$k][$j] /= $this->Rdiag[$k];
}
for ($i = 0; $i < $k; ++$i) {
for ($j = 0; $j < $nx; ++$j) {
$X[$i][$j] -= $X[$k][$j] * $this->QR[$i][$k];
}
}
}
$X = new Matrix($X);
return $X->getMatrix(0, $this->n - 1, 0, $nx);
}
throw new CalculationException(self::MATRIX_RANK_EXCEPTION);
}
throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
}
}

View File

@@ -1,31 +0,0 @@
<?php
/**
* Pythagorean Theorem:.
*
* a = 3
* b = 4
* r = sqrt(square(a) + square(b))
* r = 5
*
* r = sqrt(a^2 + b^2) without under/overflow.
*
* @param mixed $a
* @param mixed $b
*
* @return float
*/
function hypo($a, $b)
{
if (abs($a) > abs($b)) {
$r = $b / $a;
$r = abs($a) * sqrt(1 + $r * $r);
} elseif ($b != 0) {
$r = $a / $b;
$r = abs($b) * sqrt(1 + $r * $r);
} else {
$r = 0.0;
}
return $r;
}

View File

@@ -2,7 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
use PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix;
use Matrix\Matrix;
// Phpstan and Scrutinizer seem to have legitimate complaints.
// $this->slope is specified where an array is expected in several places.
@@ -167,8 +167,8 @@ class PolynomialBestFit extends BestFit
$C = $matrixA->solve($matrixB);
$coefficients = [];
for ($i = 0; $i < $C->getRowDimension(); ++$i) {
$r = $C->get($i, 0);
for ($i = 0; $i < $C->rows; ++$i) {
$r = $C->getValue($i + 1, 1); // row and column are origin-1
if (abs($r) <= 10 ** (-9)) {
$r = 0;
}

View File

@@ -21,7 +21,7 @@ class Xls
public static function sizeCol(Worksheet $worksheet, $col = 'A')
{
// default font of the workbook
$font = $worksheet->getParent()->getDefaultStyle()->getFont();
$font = $worksheet->getParentOrThrow()->getDefaultStyle()->getFont();
$columnDimensions = $worksheet->getColumnDimensions();
@@ -64,7 +64,7 @@ class Xls
public static function sizeRow(Worksheet $worksheet, $row = 1)
{
// default font of the workbook
$font = $worksheet->getParent()->getDefaultStyle()->getFont();
$font = $worksheet->getParentOrThrow()->getDefaultStyle()->getFont();
$rowDimensions = $worksheet->getRowDimensions();

View File

@@ -643,7 +643,7 @@ class Spreadsheet implements JsonSerializable
}
}
if ($worksheet->getParent() === null) { // @phpstan-ignore-line
if ($worksheet->getParent() === null) {
$worksheet->rebindParent($this);
}
@@ -871,7 +871,7 @@ class Spreadsheet implements JsonSerializable
$countCellXfs = count($this->cellXfCollection);
// copy all the shared cellXfs from the external workbook and append them to the current
foreach ($worksheet->getParent()->getCellXfCollection() as $cellXf) {
foreach ($worksheet->getParentOrThrow()->getCellXfCollection() as $cellXf) {
$this->addCellXf(clone $cellXf);
}

View File

@@ -193,7 +193,7 @@ class CellMatcher
}
if (!empty($matches[4])) {
$worksheet = $this->worksheet->getParent()->getSheetByName(trim($matches[4], "'"));
$worksheet = $this->worksheet->getParentOrThrow()->getSheetByName(trim($matches[4], "'"));
if ($worksheet === null) {
return $this->wrapValue(null);
}

View File

@@ -130,7 +130,7 @@ class Style extends Supervisor
$xfIndex = 0;
}
return $activeSheet->getParent()->getCellXfByIndex($xfIndex);
return $activeSheet->getParentOrThrow()->getCellXfByIndex($xfIndex);
}
/**
@@ -138,7 +138,7 @@ class Style extends Supervisor
*/
public function getParent(): Spreadsheet
{
return $this->getActiveSheet()->getParent();
return $this->getActiveSheet()->getParentOrThrow();
}
/**
@@ -372,7 +372,7 @@ class Style extends Supervisor
$oldXfIndexes = $this->getOldXfIndexes($selectionType, $rangeStartIndexes, $rangeEndIndexes, $columnStart, $columnEnd, $styleArray);
// clone each of the affected styles, apply the style array, and add the new styles to the workbook
$workbook = $this->getActiveSheet()->getParent();
$workbook = $this->getActiveSheet()->getParentOrThrow();
$newXfIndexes = [];
foreach ($oldXfIndexes as $oldXfIndex => $dummy) {
$style = $workbook->getCellXfByIndex($oldXfIndex);

View File

@@ -17,6 +17,10 @@ abstract class CellIterator implements NativeIterator
public const TREAT_EMPTY_STRING_AS_EMPTY_CELL = 2;
public const IF_NOT_EXISTS_RETURN_NULL = false;
public const IF_NOT_EXISTS_CREATE_NEW = true;
/**
* Worksheet to iterate.
*
@@ -38,6 +42,14 @@ abstract class CellIterator implements NativeIterator
*/
protected $onlyExistingCells = false;
/**
* If iterating all cells, and a cell doesn't exist, identifies whether a new cell should be created,
* or if the iterator should return a null value.
*
* @var bool
*/
protected $ifNotExists = self::IF_NOT_EXISTS_CREATE_NEW;
/**
* Destructor.
*/
@@ -47,6 +59,16 @@ abstract class CellIterator implements NativeIterator
$this->worksheet = $this->cellCollection = null;
}
public function getIfNotExists(): bool
{
return $this->ifNotExists;
}
public function setIfNotExists(bool $ifNotExists = self::IF_NOT_EXISTS_CREATE_NEW): void
{
$this->ifNotExists = $ifNotExists;
}
/**
* Get loop only existing cells.
*/
@@ -56,9 +78,9 @@ abstract class CellIterator implements NativeIterator
}
/**
* Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
* Validate start/end values for 'IterateOnlyExistingCells' mode, and adjust if necessary.
*/
abstract protected function adjustForExistingOnlyRange();
abstract protected function adjustForExistingOnlyRange(): void;
/**
* Set the iterator to loop only existing cells.

View File

@@ -52,14 +52,23 @@ class Column
*
* @param int $startRow The row number at which to start iterating
* @param int $endRow Optionally, the row number at which to stop iterating
*
* @return ColumnCellIterator
*/
public function getCellIterator($startRow = 1, $endRow = null)
public function getCellIterator($startRow = 1, $endRow = null): ColumnCellIterator
{
return new ColumnCellIterator($this->worksheet, $this->columnIndex, $startRow, $endRow);
}
/**
* Get row iterator. Synonym for getCellIterator().
*
* @param int $startRow The row number at which to start iterating
* @param int $endRow Optionally, the row number at which to stop iterating
*/
public function getRowIterator($startRow = 1, $endRow = null): ColumnCellIterator
{
return $this->getCellIterator($startRow, $endRow);
}
/**
* Returns a boolean true if the column contains no cells. By default, this means that no cell records exist in the
* collection for this column. false will be returned otherwise.
@@ -76,13 +85,15 @@ class Column
* Possible Flag Values are:
* CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL
* CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL
* @param int $startRow The row number at which to start iterating
* @param int $endRow Optionally, the row number at which to stop iterating
*/
public function isEmpty(int $definitionOfEmptyFlags = 0): bool
public function isEmpty(int $definitionOfEmptyFlags = 0, $startRow = 1, $endRow = null): bool
{
$nullValueCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL);
$emptyStringCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL);
$cellIterator = $this->getCellIterator();
$cellIterator = $this->getCellIterator($startRow, $endRow);
$cellIterator->setIterateOnlyExistingCells(true);
foreach ($cellIterator as $cell) {
/** @scrutinizer ignore-call */

View File

@@ -128,7 +128,11 @@ class ColumnCellIterator extends CellIterator
return $this->cellCollection->has($cellAddress)
? $this->cellCollection->get($cellAddress)
: $this->worksheet->createNewCell($cellAddress);
: (
$this->ifNotExists === self::IF_NOT_EXISTS_CREATE_NEW
? $this->worksheet->createNewCell($cellAddress)
: null
);
}
/**

View File

@@ -10,7 +10,7 @@ class ColumnDimension extends Dimension
/**
* Column index.
*
* @var string
* @var ?string
*/
private $columnIndex;
@@ -33,7 +33,7 @@ class ColumnDimension extends Dimension
/**
* Create a new ColumnDimension.
*
* @param string $index Character column index
* @param ?string $index Character column index
*/
public function __construct($index = 'A')
{
@@ -47,7 +47,7 @@ class ColumnDimension extends Dimension
/**
* Get column index as string eg: 'A'.
*/
public function getColumnIndex(): string
public function getColumnIndex(): ?string
{
return $this->columnIndex;
}
@@ -67,7 +67,7 @@ class ColumnDimension extends Dimension
*/
public function getColumnNumeric(): int
{
return Coordinate::columnIndexFromString($this->columnIndex);
return Coordinate::columnIndexFromString($this->columnIndex ?? '');
}
/**

View File

@@ -220,7 +220,7 @@ class Shadow implements IComparable
*
* @return $this
*/
public function setColor(?Color $color = null)
public function setColor(Color $color)
{
$this->color = $color;

View File

@@ -639,7 +639,7 @@ class PageSetup
public function getPrintArea($index = 0)
{
if ($index == 0) {
return $this->printArea;
return (string) $this->printArea;
}
$printAreas = explode(',', (string) $this->printArea);
if (isset($printAreas[$index - 1])) {

View File

@@ -51,14 +51,23 @@ class Row
*
* @param string $startColumn The column address at which to start iterating
* @param string $endColumn Optionally, the column address at which to stop iterating
*
* @return RowCellIterator
*/
public function getCellIterator($startColumn = 'A', $endColumn = null)
public function getCellIterator($startColumn = 'A', $endColumn = null): RowCellIterator
{
return new RowCellIterator($this->worksheet, $this->rowIndex, $startColumn, $endColumn);
}
/**
* Get column iterator. Synonym for getCellIterator().
*
* @param string $startColumn The column address at which to start iterating
* @param string $endColumn Optionally, the column address at which to stop iterating
*/
public function getColumnIterator($startColumn = 'A', $endColumn = null): RowCellIterator
{
return $this->getCellIterator($startColumn, $endColumn);
}
/**
* Returns a boolean true if the row contains no cells. By default, this means that no cell records exist in the
* collection for this row. false will be returned otherwise.
@@ -75,13 +84,15 @@ class Row
* Possible Flag Values are:
* CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL
* CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL
* @param string $startColumn The column address at which to start iterating
* @param string $endColumn Optionally, the column address at which to stop iterating
*/
public function isEmpty(int $definitionOfEmptyFlags = 0): bool
public function isEmpty(int $definitionOfEmptyFlags = 0, $startColumn = 'A', $endColumn = null): bool
{
$nullValueCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL);
$emptyStringCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL);
$cellIterator = $this->getCellIterator();
$cellIterator = $this->getCellIterator($startColumn, $endColumn);
$cellIterator->setIterateOnlyExistingCells(true);
foreach ($cellIterator as $cell) {
/** @scrutinizer ignore-call */

View File

@@ -127,7 +127,11 @@ class RowCellIterator extends CellIterator
return $this->cellCollection->has($cellAddress)
? $this->cellCollection->get($cellAddress)
: $this->worksheet->createNewCell($cellAddress);
: (
$this->ifNotExists === self::IF_NOT_EXISTS_CREATE_NEW
? $this->worksheet->createNewCell($cellAddress)
: null
);
}
/**

View File

@@ -9,7 +9,7 @@ class RowDimension extends Dimension
/**
* Row index.
*
* @var int
* @var ?int
*/
private $rowIndex;
@@ -32,7 +32,7 @@ class RowDimension extends Dimension
/**
* Create a new RowDimension.
*
* @param int $index Numeric row index
* @param ?int $index Numeric row index
*/
public function __construct($index = 0)
{
@@ -46,7 +46,7 @@ class RowDimension extends Dimension
/**
* Get Row Index.
*/
public function getRowIndex(): int
public function getRowIndex(): ?int
{
return $this->rowIndex;
}

View File

@@ -53,6 +53,11 @@ class RowIterator implements NativeIterator
$this->resetStart($startRow);
}
public function __destruct()
{
$this->subject = null; // @phpstan-ignore-line
}
/**
* (Re)Set the start row and the current row pointer.
*

View File

@@ -148,7 +148,7 @@ class Table
$tableName = StringHelper::strToLower($name);
if ($worksheet !== null && StringHelper::strToLower($this->name) !== $name) {
$spreadsheet = $worksheet->getParent();
$spreadsheet = $worksheet->getParentOrThrow();
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
foreach ($sheet->getTableCollection() as $table) {
@@ -170,7 +170,7 @@ class Table
if (StringHelper::strToLower($this->name) !== StringHelper::strToLower($name)) {
// We need to check all formula cells that might contain fully-qualified Structured References
// that refer to this table, and update those formulae to reference the new table name
$spreadsheet = $this->workSheet->getParent();
$spreadsheet = $this->workSheet->getParentOrThrow();
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
$this->updateStructuredReferencesInCells($sheet, $name);
}
@@ -298,8 +298,8 @@ class Table
}
[$width, $height] = Coordinate::rangeDimension($range);
if ($width < 1 || $height < 2) {
throw new PhpSpreadsheetException('The table range must be at least 1 column and 2 rows');
if ($width < 1 || $height < 1) {
throw new PhpSpreadsheetException('The table range must be at least 1 column and row');
}
$this->range = $range;
@@ -347,7 +347,7 @@ class Table
public function setWorksheet(?Worksheet $worksheet = null): self
{
if ($this->name !== '' && $worksheet !== null) {
$spreadsheet = $worksheet->getParent();
$spreadsheet = $worksheet->getParentOrThrow();
$tableName = StringHelper::strToUpper($this->name);
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {

View File

@@ -205,9 +205,9 @@ class Column
return $this;
}
public static function updateStructuredReferences(?Worksheet $workSheet, ?string $oldTitle, string $newTitle): void
public static function updateStructuredReferences(?Worksheet $workSheet, ?string $oldTitle, ?string $newTitle): void
{
if ($workSheet === null || $oldTitle === null || $oldTitle === '') {
if ($workSheet === null || $oldTitle === null || $oldTitle === '' || $newTitle === null) {
return;
}
@@ -215,7 +215,7 @@ class Column
if (StringHelper::strToLower($oldTitle) !== StringHelper::strToLower($newTitle)) {
// We need to check all formula cells that might contain Structured References that refer
// to this column, and update those formulae to reference the new column text
$spreadsheet = $workSheet->getParent();
$spreadsheet = $workSheet->getParentOrThrow();
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
self::updateStructuredReferencesInCells($sheet, $oldTitle, $newTitle);
}

View File

@@ -103,7 +103,7 @@ class Validations
$coordinate = strtoupper($coordinate);
// Eliminate leading equal sign
$testCoordinate = (string) preg_replace('/^=/', '', $coordinate);
$defined = $worksheet->getParent()->getDefinedName($testCoordinate, $worksheet);
$defined = $worksheet->getParentOrThrow()->getDefinedName($testCoordinate, $worksheet);
if ($defined !== null) {
if ($defined->getWorksheet() === $worksheet && !$defined->isFormula()) {
$coordinate = (string) preg_replace('/^=/', '', $defined->getValue());

View File

@@ -64,7 +64,7 @@ class Worksheet implements IComparable
/**
* Parent spreadsheet.
*
* @var Spreadsheet
* @var ?Spreadsheet
*/
private $parent;
@@ -399,7 +399,6 @@ class Worksheet implements IComparable
$this->cellCollection = null;
}
// detach ourself from the workbook, so that it can then delete this worksheet successfully
// @phpstan-ignore-next-line
$this->parent = null;
}
@@ -644,13 +643,9 @@ class Worksheet implements IComparable
*/
public function getChartByName($chartName)
{
$chartCount = count($this->chartCollection);
if ($chartCount == 0) {
return false;
}
foreach ($this->chartCollection as $index => $chart) {
if ($chart->getName() == $chartName) {
return $this->chartCollection[$index];
return $chart;
}
}
@@ -760,7 +755,7 @@ class Worksheet implements IComparable
//The only exception is if it's a merge range value cell of a 'vertical' range (1 column wide)
if ($isMerged && $cell->isMergeRangeValueCell()) {
$range = $cell->getMergeRange();
$range = (string) $cell->getMergeRange();
$rangeBoundaries = Coordinate::rangeDimension($range);
if ($rangeBoundaries[0] === 1) {
$isMergedButProceed = true;
@@ -782,7 +777,7 @@ class Worksheet implements IComparable
// To formatted string
$cellValue = NumberFormat::toFormattedString(
$cell->getCalculatedValue(),
$this->getParent()->getCellXfByIndex($cell->getXfIndex())
(string) $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())
->getNumberFormat()->getFormatCode()
);
@@ -790,11 +785,11 @@ class Worksheet implements IComparable
$autoSizes[$this->cellCollection->getCurrentColumn()] = max(
(float) $autoSizes[$this->cellCollection->getCurrentColumn()],
(float) Shared\Font::calculateColumnWidth(
$this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
$this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())->getFont(),
$cellValue,
$this->getParent()->getCellXfByIndex($cell->getXfIndex())
(int) $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())
->getAlignment()->getTextRotation(),
$this->getParent()->getDefaultStyle()->getFont(),
$this->getParentOrThrow()->getDefaultStyle()->getFont(),
$filterAdjustment,
$indentAdjustment
)
@@ -817,15 +812,25 @@ class Worksheet implements IComparable
}
/**
* Get parent.
*
* @return Spreadsheet
* Get parent or null.
*/
public function getParent()
public function getParent(): ?Spreadsheet
{
return $this->parent;
}
/**
* Get parent, throw exception if null.
*/
public function getParentOrThrow(): Spreadsheet
{
if ($this->parent !== null) {
return $this->parent;
}
throw new Exception('Sheet does not have a parent.');
}
/**
* Re-bind parent.
*
@@ -1274,7 +1279,7 @@ class Worksheet implements IComparable
if (strpos($coordinate, '!') !== false) {
$worksheetReference = self::extractSheetTitle($coordinate, true);
$sheet = $this->parent->getSheetByName($worksheetReference[0]);
$sheet = $this->getParentOrThrow()->getSheetByName($worksheetReference[0]);
$finalCoordinate = strtoupper($worksheetReference[1]);
if ($sheet === null) {
@@ -1509,12 +1514,12 @@ class Worksheet implements IComparable
$cellCoordinate = Validations::validateCellOrCellRange($cellCoordinate);
// set this sheet as active
$this->parent->setActiveSheetIndex($this->parent->getIndex($this));
$this->getParentOrThrow()->setActiveSheetIndex($this->getParentOrThrow()->getIndex($this));
// set cell coordinate as active
$this->setSelectedCells($cellCoordinate);
return $this->parent->getCellXfSupervisor();
return $this->getParentOrThrow()->getCellXfSupervisor();
}
/**
@@ -1666,8 +1671,8 @@ class Worksheet implements IComparable
public function duplicateStyle(Style $style, $range)
{
// Add the style to the workbook if necessary
$workbook = $this->parent;
if ($existingStyle = $this->parent->getCellXfByHashCode($style->getHashCode())) {
$workbook = $this->getParentOrThrow();
if ($existingStyle = $workbook->getCellXfByHashCode($style->getHashCode())) {
// there is already such cell Xf in our collection
$xfIndex = $existingStyle->getIndex();
} else {
@@ -1899,7 +1904,7 @@ class Worksheet implements IComparable
public function mergeCellBehaviour(Cell $cell, string $upperLeft, string $behaviour, array $leftCellValue): array
{
if ($cell->getCoordinate() !== $upperLeft) {
Calculation::getInstance($cell->getWorksheet()->getParent())->flushInstance();
Calculation::getInstance($cell->getWorksheet()->getParentOrThrow())->flushInstance();
if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) {
$cellValue = $cell->getFormattedValue();
if ($cellValue !== '') {
@@ -2236,6 +2241,12 @@ class Worksheet implements IComparable
return $tableNames;
}
/** @var null|Table */
private static $scrutinizerNullTable;
/** @var null|int */
private static $scrutinizerNullInt;
/**
* @param string $name the table name to search
*
@@ -2245,7 +2256,7 @@ class Worksheet implements IComparable
{
$tableIndex = $this->getTableIndexByName($name);
return ($tableIndex === null) ? null : $this->tableCollection[$tableIndex];
return ($tableIndex === null) ? self::$scrutinizerNullTable : $this->tableCollection[$tableIndex];
}
/**
@@ -2263,7 +2274,7 @@ class Worksheet implements IComparable
}
}
return null;
return self::$scrutinizerNullInt;
}
/**
@@ -2495,7 +2506,7 @@ class Worksheet implements IComparable
} elseif ($num > $highRow) {
$num -= $numberOfRows;
$cloneDimension = clone $rowDimension;
$cloneDimension->setRowIndex($num);
$cloneDimension->setRowIndex(/** @scrutinizer ignore-type */ $num);
$holdRowDimensions[$num] = $cloneDimension;
}
}
@@ -2967,9 +2978,10 @@ class Worksheet implements IComparable
$cRef = $returnCellRef ? $col : ++$c;
// Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
// so we test and retrieve directly against cellCollection
if ($this->cellCollection->has($col . $row)) {
$cell = $this->cellCollection->get($col . $row);
//if ($this->cellCollection->has($col . $row)) {
if ($cell !== null) {
// Cell exists
$cell = $this->cellCollection->get($col . $row);
if ($cell->getValue() !== null) {
if ($cell->getValue() instanceof RichText) {
$returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
@@ -2982,10 +2994,10 @@ class Worksheet implements IComparable
}
if ($formatData) {
$style = $this->parent->getCellXfByIndex($cell->getXfIndex());
$style = $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex());
$returnValue[$rRef][$cRef] = NumberFormat::toFormattedString(
$returnValue[$rRef][$cRef],
($style && $style->getNumberFormat()) ? $style->getNumberFormat()->getFormatCode() : NumberFormat::FORMAT_GENERAL
$style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL
);
}
} else {
@@ -3022,14 +3034,17 @@ class Worksheet implements IComparable
throw new Exception('Defined Named ' . $definedName . ' is a formula, not a range or cell.');
}
if ($namedRange->getLocalOnly() && $this->getHashCode() !== $namedRange->getWorksheet()->getHashCode()) {
if ($returnNullIfInvalid) {
return null;
}
if ($namedRange->getLocalOnly()) {
$worksheet = $namedRange->getWorksheet();
if ($worksheet === null || $this->getHashCode() !== $worksheet->getHashCode()) {
if ($returnNullIfInvalid) {
return null;
}
throw new Exception(
'Named range ' . $definedName . ' is not accessible from within sheet ' . $this->getTitle()
);
throw new Exception(
'Named range ' . $definedName . ' is not accessible from within sheet ' . $this->getTitle()
);
}
}
return $namedRange;
@@ -3049,13 +3064,18 @@ class Worksheet implements IComparable
*/
public function namedRangeToArray(string $definedName, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
{
$retVal = [];
$namedRange = $this->validateNamedRange($definedName);
$workSheet = $namedRange->getWorksheet();
/** @phpstan-ignore-next-line */
$cellRange = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!');
$cellRange = str_replace('$', '', $cellRange);
if ($namedRange !== null) {
$cellRange = ltrim(substr($namedRange->getValue(), (int) strrpos($namedRange->getValue(), '!')), '!');
$cellRange = str_replace('$', '', $cellRange);
$workSheet = $namedRange->getWorksheet();
if ($workSheet !== null) {
$retVal = $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
}
}
return $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
return $retVal;
}
/**
@@ -3516,16 +3536,16 @@ class Worksheet implements IComparable
// We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
if ($this->getParent()) {
if ($this->parent !== null) {
// Is there already such sheet name?
if ($this->getParent()->sheetCodeNameExists($codeName)) {
if ($this->parent->sheetCodeNameExists($codeName)) {
// Use name, but append with lowest possible integer
if (Shared\StringHelper::countCharacters($codeName) > 29) {
$codeName = Shared\StringHelper::substring($codeName, 0, 29);
}
$i = 1;
while ($this->getParent()->sheetCodeNameExists($codeName . '_' . $i)) {
while ($this->getParentOrThrow()->sheetCodeNameExists($codeName . '_' . $i)) {
++$i;
if ($i == 10) {
if (Shared\StringHelper::countCharacters($codeName) > 28) {

View File

@@ -803,7 +803,12 @@ class Html extends BaseWriter
private function buildCssPerSheet(Worksheet $sheet, array &$css): void
{
// Calculate hash code
$sheetIndex = $sheet->getParent()->getIndex($sheet);
$sheetIndex = $sheet->getParentOrThrow()->getIndex($sheet);
$setup = $sheet->getPageSetup();
if ($setup->getFitToPage() && $setup->getFitToHeight() === 1) {
$css["table.sheet$sheetIndex"]['page-break-inside'] = 'avoid';
$css["table.sheet$sheetIndex"]['break-inside'] = 'avoid';
}
// Build styles
// Calculate column widths
@@ -1139,7 +1144,7 @@ class Html extends BaseWriter
*/
private function generateTableHeader(Worksheet $worksheet, $showid = true)
{
$sheetIndex = $worksheet->getParent()->getIndex($worksheet);
$sheetIndex = $worksheet->getParentOrThrow()->getIndex($worksheet);
// Construct HTML
$html = '';
@@ -1284,7 +1289,7 @@ class Html extends BaseWriter
$this->generateRowCellDataValueRich($cell, $cellData);
} else {
$origData = $this->preCalculateFormulas ? $cell->getCalculatedValue() : $cell->getValue();
$formatCode = $worksheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode();
$formatCode = $worksheet->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode();
$cellData = NumberFormat::toFormattedString(
$origData ?? '',
@@ -1295,9 +1300,9 @@ class Html extends BaseWriter
if ($cellData === $origData) {
$cellData = htmlspecialchars($cellData, Settings::htmlEntityFlags());
}
if ($worksheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) {
if ($worksheet->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) {
$cellData = '<sup>' . $cellData . '</sup>';
} elseif ($worksheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubscript()) {
} elseif ($worksheet->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubscript()) {
$cellData = '<sub>' . $cellData . '</sub>';
}
}
@@ -1342,7 +1347,7 @@ class Html extends BaseWriter
}
// General horizontal alignment: Actual horizontal alignment depends on dataType
$sharedStyle = $worksheet->getParent()->getCellXfByIndex($cell->getXfIndex());
$sharedStyle = $worksheet->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex());
if (
$sharedStyle->getAlignment()->getHorizontal() == Alignment::HORIZONTAL_GENERAL
&& isset($this->cssStyles['.' . $cell->getDataType()]['text-align'])
@@ -1449,7 +1454,7 @@ class Html extends BaseWriter
private function generateRow(Worksheet $worksheet, array $values, $row, $cellType)
{
// Sheet index
$sheetIndex = $worksheet->getParent()->getIndex($worksheet);
$sheetIndex = $worksheet->getParentOrThrow()->getIndex($worksheet);
$html = $this->generateRowStart($worksheet, $sheetIndex, $row);
$generateDiv = $this->isMPdf && $worksheet->getRowDimension($row + 1)->getVisible() === false;
if ($generateDiv) {
@@ -1470,14 +1475,14 @@ class Html extends BaseWriter
}
// Should the cell be written or is it swallowed by a rowspan or colspan?
$writeCell = !(isset($this->isSpannedCell[$worksheet->getParent()->getIndex($worksheet)][$row + 1][$colNum])
&& $this->isSpannedCell[$worksheet->getParent()->getIndex($worksheet)][$row + 1][$colNum]);
$writeCell = !(isset($this->isSpannedCell[$worksheet->getParentOrThrow()->getIndex($worksheet)][$row + 1][$colNum])
&& $this->isSpannedCell[$worksheet->getParentOrThrow()->getIndex($worksheet)][$row + 1][$colNum]);
// Colspan and Rowspan
$colSpan = 1;
$rowSpan = 1;
if (isset($this->isBaseCell[$worksheet->getParent()->getIndex($worksheet)][$row + 1][$colNum])) {
$spans = $this->isBaseCell[$worksheet->getParent()->getIndex($worksheet)][$row + 1][$colNum];
if (isset($this->isBaseCell[$worksheet->getParentOrThrow()->getIndex($worksheet)][$row + 1][$colNum])) {
$spans = $this->isBaseCell[$worksheet->getParentOrThrow()->getIndex($worksheet)][$row + 1][$colNum];
$rowSpan = $spans['rowspan'];
$colSpan = $spans['colspan'];

View File

@@ -245,7 +245,7 @@ class Xls extends BaseWriter
foreach ($this->spreadsheet->getAllsheets() as $sheet) {
// sheet index
$sheetIndex = $sheet->getParent()->getIndex($sheet);
$sheetIndex = $sheet->getParentOrThrow()->getIndex($sheet);
// check if there are any shapes for this sheet
$filterRange = $sheet->getAutoFilter()->getRange();
@@ -260,7 +260,7 @@ class Xls extends BaseWriter
$dgContainer = new DgContainer();
// set the drawing index (we use sheet index + 1)
$dgId = $sheet->getParent()->getIndex($sheet) + 1;
$dgId = $sheet->getParentOrThrow()->getIndex($sheet) + 1;
$dgContainer->setDgId($dgId);
$escher->setDgContainer($dgContainer);
@@ -272,7 +272,7 @@ class Xls extends BaseWriter
$spContainer = new SpContainer();
$spContainer->setSpgr(true);
$spContainer->setSpType(0);
$spContainer->setSpId(($sheet->getParent()->getIndex($sheet) + 1) << 10);
$spContainer->setSpId(($sheet->getParentOrThrow()->getIndex($sheet) + 1) << 10);
$spgrContainer->addChild($spContainer);
// add the shapes
@@ -294,7 +294,7 @@ class Xls extends BaseWriter
// set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
$reducedSpId = $countShapes[$sheetIndex];
$spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
$spId = $reducedSpId | ($sheet->getParentOrThrow()->getIndex($sheet) + 1) << 10;
$spContainer->setSpId($spId);
// keep track of last reducedSpId
@@ -351,7 +351,7 @@ class Xls extends BaseWriter
// set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
$reducedSpId = $countShapes[$sheetIndex];
$spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
$spId = $reducedSpId | ($sheet->getParentOrThrow()->getIndex($sheet) + 1) << 10;
$spContainer->setSpId($spId);
// keep track of last reducedSpId

View File

@@ -603,12 +603,9 @@ class Workbook extends BIFFwriter
}
if ($definedName->getLocalOnly()) {
/**
* local scope.
*
* @phpstan-ignore-next-line
*/
$scope = $this->spreadsheet->getIndex($definedName->getScope()) + 1;
// local scope
$scopeWs = $definedName->getScope();
$scope = ($scopeWs === null) ? 0 : ($this->spreadsheet->getIndex($scopeWs) + 1);
} else {
// global scope
$scope = 0;

View File

@@ -273,7 +273,7 @@ class Worksheet extends BIFFwriter
$this->lastColumnIndex = 255;
}
$this->countCellStyleXfs = count($phpSheet->getParent()->getCellStyleXfCollection());
$this->countCellStyleXfs = count($phpSheet->getParentOrThrow()->getCellStyleXfCollection());
}
/**
@@ -288,7 +288,7 @@ class Worksheet extends BIFFwriter
// Storing selected cells and active sheet because it changes while parsing cells with formulas.
$selectedCells = $this->phpSheet->getSelectedCells();
$activeSheetIndex = $this->phpSheet->getParent()->getActiveSheetIndex();
$activeSheetIndex = $this->phpSheet->getParentOrThrow()->getActiveSheetIndex();
// Write BOF record
$this->storeBof(0x0010);
@@ -307,7 +307,7 @@ class Worksheet extends BIFFwriter
// Column dimensions
if (($defaultWidth = $phpSheet->getDefaultColumnDimension()->getWidth()) < 0) {
$defaultWidth = \PhpOffice\PhpSpreadsheet\Shared\Font::getDefaultColumnWidthByFont($phpSheet->getParent()->getDefaultStyle()->getFont());
$defaultWidth = \PhpOffice\PhpSpreadsheet\Shared\Font::getDefaultColumnWidthByFont($phpSheet->getParentOrThrow()->getDefaultStyle()->getFont());
}
$columnDimensions = $phpSheet->getColumnDimensions();
@@ -498,7 +498,7 @@ class Worksheet extends BIFFwriter
$this->writeMsoDrawing();
// Restoring active sheet.
$this->phpSheet->getParent()->setActiveSheetIndex($activeSheetIndex);
$this->phpSheet->getParentOrThrow()->setActiveSheetIndex($activeSheetIndex);
// Write WINDOW2 record
$this->writeWindow2();
@@ -1255,7 +1255,7 @@ class Worksheet extends BIFFwriter
$fDspGuts = $this->outlineOn; // 7
$fFrozenNoSplit = 0; // 0 - bit
// no support in PhpSpreadsheet for selected sheet, therefore sheet is only selected if it is the active sheet
$fSelected = ($this->phpSheet === $this->phpSheet->getParent()->getActiveSheet()) ? 1 : 0;
$fSelected = ($this->phpSheet === $this->phpSheet->getParentOrThrow()->getActiveSheet()) ? 1 : 0;
$fPageBreakPreview = $this->phpSheet->getSheetView()->getView() === SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW;
$grbit = $fDspFmla;
@@ -1677,7 +1677,7 @@ class Worksheet extends BIFFwriter
// Order of printing pages
$fLeftToRight = $this->phpSheet->getPageSetup()->getPageOrder() === PageSetup::PAGEORDER_DOWN_THEN_OVER
? 0x1 : 0x0;
? 0x0 : 0x1;
// Page orientation
$fLandscape = ($this->phpSheet->getPageSetup()->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE)
? 0x0 : 0x1;

View File

@@ -455,14 +455,17 @@ class Xlsx extends BaseWriter
}
// Add comment relationship parts
if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) {
$legacy = $unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['legacyDrawing'] ?? null;
if (count($this->spreadSheet->getSheet($i)->getComments()) > 0 || $legacy !== null) {
// VML Comments relationships
$zipContent['xl/drawings/_rels/vmlDrawing' . ($i + 1) . '.vml.rels'] = $this->getWriterPartRels()->writeVMLDrawingRelationships($this->spreadSheet->getSheet($i));
// VML Comments
$zipContent['xl/drawings/vmlDrawing' . ($i + 1) . '.vml'] = $this->getWriterPartComments()->writeVMLComments($this->spreadSheet->getSheet($i));
$zipContent['xl/drawings/vmlDrawing' . ($i + 1) . '.vml'] = $legacy ?? $this->getWriterPartComments()->writeVMLComments($this->spreadSheet->getSheet($i));
}
// Comments
// Comments
if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) {
$zipContent['xl/comments' . ($i + 1) . '.xml'] = $this->getWriterPartComments()->writeComments($this->spreadSheet->getSheet($i));
// Media
@@ -477,7 +480,9 @@ class Xlsx extends BaseWriter
// Add unparsed relationship parts
if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) {
foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) {
$zipContent[$vmlDrawing['filePath']] = $vmlDrawing['content'];
if (!isset($zipContent[$vmlDrawing['filePath']])) {
$zipContent[$vmlDrawing['filePath']] = $vmlDrawing['content'];
}
}
}

View File

@@ -1041,11 +1041,12 @@ class Chart extends WriterPart
$objWriter->startElement('c:ser');
$objWriter->startElement('c:idx');
$objWriter->writeAttribute('val', (string) ($this->seriesIndex + $plotSeriesIdx));
$adder = array_key_exists(0, $plotSeriesOrder) ? $this->seriesIndex : 0;
$objWriter->writeAttribute('val', (string) ($adder + $plotSeriesIdx));
$objWriter->endElement();
$objWriter->startElement('c:order');
$objWriter->writeAttribute('val', (string) ($this->seriesIndex + $plotSeriesRef));
$objWriter->writeAttribute('val', (string) ($adder + $plotSeriesRef));
$objWriter->endElement();
$plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx);

View File

@@ -72,7 +72,7 @@ class DefinedNames
$local = -1;
if ($definedName->getLocalOnly() && $definedName->getScope() !== null) {
try {
$local = $definedName->getScope()->getParent()->getIndex($definedName->getScope());
$local = $definedName->getScope()->getParentOrThrow()->getIndex($definedName->getScope());
} catch (Exception $e) {
// See issue 2266 - deleting sheet which contains
// defined names will cause Exception above.

View File

@@ -67,7 +67,7 @@ class Drawing extends WriterPart
}
// unparsed AlternateContent
$unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData();
$unparsedLoadedData = $worksheet->getParentOrThrow()->getUnparsedLoadedData();
if (isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingAlternateContents'])) {
foreach ($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) {
$objWriter->writeRaw($drawingAlternateContent);

View File

@@ -188,7 +188,7 @@ class Rels extends WriterPart
// Write drawing relationships?
$drawingOriginalIds = [];
$unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData();
$unparsedLoadedData = $worksheet->getParentOrThrow()->getUnparsedLoadedData();
if (isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds'])) {
$drawingOriginalIds = $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds'];
}
@@ -239,14 +239,16 @@ class Rels extends WriterPart
// Write comments relationship?
$i = 1;
if (count($worksheet->getComments()) > 0) {
if (count($worksheet->getComments()) > 0 || isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['legacyDrawing'])) {
$this->writeRelationship(
$objWriter,
'_comments_vml' . $i,
Namespaces::VML,
'../drawings/vmlDrawing' . $worksheetId . '.vml'
);
}
if (count($worksheet->getComments()) > 0) {
$this->writeRelationship(
$objWriter,
'_comments' . $i,
@@ -288,7 +290,7 @@ class Rels extends WriterPart
private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, XMLWriter $objWriter, string $relationship, string $type): void
{
$unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData();
$unparsedLoadedData = $worksheet->getParentOrThrow()->getUnparsedLoadedData();
if (!isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()][$relationship])) {
return;
}

View File

@@ -138,7 +138,7 @@ class Worksheet extends WriterPart
{
// sheetPr
$objWriter->startElement('sheetPr');
if ($worksheet->getParent()->hasMacros()) {
if ($worksheet->getParentOrThrow()->hasMacros()) {
//if the workbook have macros, we need to have codeName for the sheet
if (!$worksheet->hasCodeName()) {
$worksheet->setCodeName($worksheet->getTitle());
@@ -969,7 +969,7 @@ class Worksheet extends WriterPart
}
$objWriter->writeAttribute('pageOrder', $worksheet->getPageSetup()->getPageOrder());
$getUnparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData();
$getUnparsedLoadedData = $worksheet->getParentOrThrow()->getUnparsedLoadedData();
if (isset($getUnparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'])) {
$objWriter->writeAttribute('r:id', $getUnparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId']);
}
@@ -1305,7 +1305,7 @@ class Worksheet extends WriterPart
*/
private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet, $includeCharts = false): void
{
$unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData();
$unparsedLoadedData = $worksheet->getParentOrThrow()->getUnparsedLoadedData();
$hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds']);
$chartCount = ($includeCharts) ? $worksheet->getChartCollection()->count() : 0;
if ($chartCount == 0 && $worksheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) {
@@ -1333,7 +1333,8 @@ class Worksheet extends WriterPart
private function writeLegacyDrawing(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
{
// If sheet contains comments, add the relationships
if (count($worksheet->getComments()) > 0) {
$unparsedLoadedData = $worksheet->getParentOrThrow()->getUnparsedLoadedData();
if (count($worksheet->getComments()) > 0 || isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['legacyDrawing'])) {
$objWriter->startElement('legacyDrawing');
$objWriter->writeAttribute('r:id', 'rId_comments_vml1');
$objWriter->endElement();
@@ -1355,11 +1356,11 @@ class Worksheet extends WriterPart
private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
{
if (empty($worksheet->getParent()->getUnparsedLoadedData()['sheets'][$worksheet->getCodeName()]['AlternateContents'])) {
if (empty($worksheet->getParentOrThrow()->getUnparsedLoadedData()['sheets'][$worksheet->getCodeName()]['AlternateContents'])) {
return;
}
foreach ($worksheet->getParent()->getUnparsedLoadedData()['sheets'][$worksheet->getCodeName()]['AlternateContents'] as $alternateContent) {
foreach ($worksheet->getParentOrThrow()->getUnparsedLoadedData()['sheets'][$worksheet->getCodeName()]['AlternateContents'] as $alternateContent) {
$objWriter->writeRaw($alternateContent);
}
}