提交的内容

This commit is contained in:
2025-05-12 15:45:02 +08:00
parent 629c4750da
commit b48c692775
3043 changed files with 34732 additions and 60810 deletions

View File

View File

View File

@ -19,6 +19,7 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use ReflectionClassConstant;
use ReflectionMethod;
use ReflectionParameter;
use Throwable;
class Calculation
{
@ -3556,7 +3557,7 @@ class Calculation
}
}
throw new Exception($e->getMessage());
throw new Exception($e->getMessage(), $e->getCode(), $e);
}
if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
@ -4210,7 +4211,7 @@ class Calculation
try {
$this->branchPruner->closingBrace($d['value']);
} catch (Exception $e) {
return $this->raiseFormulaError($e->getMessage());
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
}
$functionName = $matches[1]; // Get the function name
@ -4249,7 +4250,7 @@ class Calculation
} elseif ($expectedArgumentCount != '*') {
$isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch);
self::doNothing($isOperandOrFunction);
switch ($argMatch[2]) {
switch ($argMatch[2] ?? '') {
case '+':
if ($argumentCount < $argMatch[1]) {
$argumentCountError = true;
@ -4282,7 +4283,7 @@ class Calculation
try {
$this->branchPruner->argumentSeparator();
} catch (Exception $e) {
return $this->raiseFormulaError($e->getMessage());
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
}
while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last (
@ -4364,8 +4365,12 @@ class Calculation
$rangeStartCellRef = $output[count($output) - 2]['value'] ?? '';
}
preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches);
if ($rangeStartMatches[2] > '') {
$val = $rangeStartMatches[2] . '!' . $val;
if (array_key_exists(2, $rangeStartMatches)) {
if ($rangeStartMatches[2] > '') {
$val = $rangeStartMatches[2] . '!' . $val;
}
} else {
$val = Information\ExcelError::REF();
}
} else {
$rangeStartCellRef = $output[count($output) - 1]['value'] ?? '';
@ -4391,7 +4396,7 @@ class Calculation
try {
$structuredReference = Operands\StructuredReference::fromParser($formula, $index, $matches);
} catch (Exception $e) {
return $this->raiseFormulaError($e->getMessage());
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
}
$val = $structuredReference->value();
@ -4434,6 +4439,8 @@ class Calculation
}
$val = $address;
}
} elseif ($val === Information\ExcelError::REF()) {
$stackItemReference = $val;
} else {
$startRowColRef = $output[count($output) - 1]['value'] ?? '';
[$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true);
@ -4731,7 +4738,7 @@ class Calculation
$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);
$rangeValue = self::getInstance($cell->getWorksheet()->getParent())->_calculateFormulaValue("={$cellRange}", $cellRange, $cell);
$stack->push('Value', $rangeValue);
$this->debugLog->writeDebugLog('Evaluated Structured Reference %s as value %s', $token->value(), $this->showValue($rangeValue));
} else {
@ -4745,7 +4752,7 @@ class Calculation
$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());
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
}
}
} elseif (!is_numeric($token) && !is_object($token) && isset(self::BINARY_OPERATORS[$token])) {
@ -4793,7 +4800,7 @@ class Calculation
}
}
}
if (strpos($operand1Data['reference'], '!') !== false) {
if (strpos($operand1Data['reference'] ?? '', '!') !== false) {
[$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true);
} else {
$sheet1 = ($pCellWorksheet !== null) ? $pCellWorksheet->getTitle() : '';
@ -4830,10 +4837,21 @@ class Calculation
$oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference']));
$oCol = $oRow = [];
$breakNeeded = false;
foreach ($oData as $oDatum) {
$oCR = Coordinate::coordinateFromString($oDatum);
$oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1;
$oRow[] = $oCR[1];
try {
$oCR = Coordinate::coordinateFromString($oDatum);
$oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1;
$oRow[] = $oCR[1];
} catch (\Exception $e) {
$stack->push('Error', Information\ExcelError::REF(), null);
$breakNeeded = true;
break;
}
}
if ($breakNeeded) {
break;
}
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
if ($pCellParent !== null && $this->spreadsheet !== null) {
@ -4842,8 +4860,10 @@ class Calculation
return $this->raiseFormulaError('Unable to access Cell Reference');
}
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellValue));
$stack->push('Cell Reference', $cellValue, $cellRef);
} else {
$this->debugLog->writeDebugLog('Evaluation Result is a #REF! Error');
$stack->push('Error', Information\ExcelError::REF(), null);
}
@ -5434,13 +5454,13 @@ class Calculation
*
* @return false
*/
protected function raiseFormulaError(string $errorMessage)
protected function raiseFormulaError(string $errorMessage, int $code = 0, ?Throwable $exception = null)
{
$this->formulaError = $errorMessage;
$this->cyclicReferenceStack->clear();
$suppress = /** @scrutinizer ignore-deprecated */ $this->suppressFormulaErrors ?? $this->suppressFormulaErrorsNew;
if (!$suppress) {
throw new Exception($errorMessage);
throw new Exception($errorMessage, $code, $exception);
}
return false;
@ -5603,7 +5623,7 @@ class Calculation
private function addDefaultArgumentValues(array $functionCall, array $args, array $emptyArguments): array
{
$reflector = new ReflectionMethod(implode('::', $functionCall));
$reflector = new ReflectionMethod($functionCall[0], $functionCall[1]);
$methodArguments = $reflector->getParameters();
if (count($methodArguments) > 0) {

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -45,6 +45,11 @@ class DateValue
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
}
// try to parse as date iff there is at least one digit
if (is_string($dateValue) && preg_match('/\\d/', $dateValue) !== 1) {
return ExcelError::VALUE();
}
$dti = new DateTimeImmutable();
$baseYear = SharedDateHelper::getExcelCalendar();
$dateValue = trim($dateValue ?? '', '"');

View File

@ -45,6 +45,7 @@ class Month
} catch (Exception $e) {
return $e->getMessage();
}
$dateValue = floor($dateValue);
$adjustmentMonths = floor($adjustmentMonths);
// Execute function
@ -88,6 +89,7 @@ class Month
} catch (Exception $e) {
return $e->getMessage();
}
$dateValue = floor($dateValue);
$adjustmentMonths = floor($adjustmentMonths);
// Execute function

View File

@ -42,6 +42,11 @@ class TimeValue
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
}
// try to parse as time iff there is at least one digit
if (is_string($timeValue) && preg_match('/\\d/', $timeValue) !== 1) {
return ExcelError::VALUE();
}
$timeValue = trim($timeValue ?? '', '"');
$timeValue = str_replace(['/', '.'], '-', $timeValue);

View File

@ -48,9 +48,9 @@ class FormattedNumber
*/
public static function convertToNumberIfNumeric(string &$operand): bool
{
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator());
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
$value = preg_replace(['/(\d)' . $thousandsSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1$2', '$1$2'], trim($operand));
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator());
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
if (is_numeric($value)) {
@ -90,9 +90,9 @@ class FormattedNumber
*/
public static function convertToNumberIfPercent(string &$operand): bool
{
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator());
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', trim($operand));
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator());
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
$match = [];
@ -116,17 +116,22 @@ class FormattedNumber
public static function convertToNumberIfCurrency(string &$operand): bool
{
$currencyRegexp = self::currencyMatcherRegexp();
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator());
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $operand);
$match = [];
if ($value !== null && preg_match($currencyRegexp, $value, $match, PREG_UNMATCHED_AS_NULL)) {
//Determine the sign
$sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? '';
$decimalSeparator = StringHelper::getDecimalSeparator();
//Cast to a float
$operand = (float) ($sign . ($match['PostfixedValue'] ?? $match['PrefixedValue']));
$intermediate = (string) ($match['PostfixedValue'] ?? $match['PrefixedValue']);
$intermediate = str_replace($decimalSeparator, '.', $intermediate);
if (is_numeric($intermediate)) {
$operand = (float) ($sign . str_replace($decimalSeparator, '.', $intermediate));
return true;
return true;
}
}
return false;
@ -134,8 +139,8 @@ class FormattedNumber
public static function currencyMatcherRegexp(): string
{
$currencyCodes = sprintf(self::CURRENCY_CONVERSION_LIST, preg_quote(StringHelper::getCurrencyCode()));
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator());
$currencyCodes = sprintf(self::CURRENCY_CONVERSION_LIST, preg_quote(StringHelper::getCurrencyCode(), '/'));
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
return '~^(?:(?: *(?<PrefixedSign>[-+])? *(?<PrefixedCurrency>[' . $currencyCodes . ']) *(?<PrefixedSign2>[-+])? *(?<PrefixedValue>[0-9]+[' . $decimalSeparator . ']?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?<PostfixedSign>[-+])? *(?<PostfixedValue>[0-9]+' . $decimalSeparator . '?[0-9]*(?:E[-+]?[0-9]*)?) *(?<PostfixedCurrency>[' . $currencyCodes . ']) *))$~ui';
}

View File

View File

@ -190,8 +190,8 @@ final class StructuredReference implements Operand
{
if ($columnName !== '') {
$cellReference = $columnId . $cell->getRow();
$pattern1 = '/\[' . preg_quote($columnName) . '\]/miu';
$pattern2 = '/@' . preg_quote($columnName) . '/miu';
$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) {
@ -328,7 +328,7 @@ final class StructuredReference implements Operand
$cellFrom = "{$columnId}{$startRow}";
$cellTo = "{$columnId}{$endRow}";
$cellReference = ($cellFrom === $cellTo) ? $cellFrom : "{$cellFrom}:{$cellTo}";
$pattern = '/\[' . preg_quote($columnName) . '\]/mui';
$pattern = '/\[' . preg_quote($columnName, '/') . '\]/mui';
if (preg_match($pattern, $reference) === 1) {
$columnsSelected = true;
$reference = preg_replace($pattern, $cellReference, $reference);

View File

@ -20,28 +20,6 @@ class Engineering
*/
public const EULER = 2.71828182845904523536;
/**
* parseComplex.
*
* Parses a complex number into its real and imaginary parts, and an I or J suffix
*
* @deprecated 1.12.0 No longer used by internal code. Please use the \Complex\Complex class instead
*
* @param string $complexNumber The complex number
*
* @return mixed[] Indexed on "real", "imaginary" and "suffix"
*/
public static function parseComplex($complexNumber)
{
$complex = new Complex($complexNumber);
return [
'real' => $complex->getReal(),
'imaginary' => $complex->getImaginary(),
'suffix' => $complex->getSuffix(),
];
}
/**
* BESSELI.
*

View File

@ -49,7 +49,7 @@ class Complex
return $e->getMessage();
}
if (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) {
if (($suffix === 'i') || ($suffix === 'j') || ($suffix === '')) {
$complex = new ComplexObject($realNumber, $imaginary, $suffix);
return (string) $complex;

View File

@ -40,7 +40,7 @@ class ConvertBinary extends ConvertBase
return $e->getMessage();
}
if (strlen($value) == 10) {
if (strlen($value) == 10 && $value[0] === '1') {
// Two's Complement
$value = substr($value, -9);
@ -91,7 +91,7 @@ class ConvertBinary extends ConvertBase
return $e->getMessage();
}
if (strlen($value) == 10) {
if (strlen($value) == 10 && $value[0] === '1') {
$high2 = substr($value, 0, 2);
$low8 = substr($value, 2);
$xarr = ['00' => '00000000', '01' => '00000001', '10' => 'FFFFFFFE', '11' => 'FFFFFFFF'];
@ -144,7 +144,7 @@ class ConvertBinary extends ConvertBase
return $e->getMessage();
}
if (strlen($value) == 10 && substr($value, 0, 1) === '1') { // Two's Complement
if (strlen($value) == 10 && $value[0] === '1') { // Two's Complement
return str_repeat('7', 6) . strtoupper(decoct((int) bindec("11$value")));
}
$octVal = (string) decoct((int) bindec($value));

View File

View File

View File

View File

View File

View File

@ -80,6 +80,7 @@ class Amortization
$amortiseCoeff = self::getAmortizationCoefficient($rate);
$rate *= $amortiseCoeff;
$rate = (float) (string) $rate; // ugly way to avoid rounding problem
$fNRate = round($yearFrac * $rate * $cost, 0);
$cost -= $fNRate;
$fRest = $cost - $salvage;

View File

View File

View File

View File

View File

View File

View File

View File

View File

Some files were not shown because too many files have changed in this diff Show More