Skip to content

Commit 6d2d99a

Browse files
authored
Merge pull request #4080 from oleibman/issue2581
Propagate Errors in Text Functions
2 parents 06737c1 + 22bac3e commit 6d2d99a

File tree

16 files changed

+251
-42
lines changed

16 files changed

+251
-42
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3535
- Mpdf and Tcpdf Borders on Merged Cells. [Issue #3557](https://github.com/PHPOffice/PhpSpreadsheet/issues/3557) [PR #4047](https://github.com/PHPOffice/PhpSpreadsheet/pull/4047)
3636
- Xls Conditional Format Improvements. [PR #4030](https://github.com/PHPOffice/PhpSpreadsheet/pull/4030) [PR #4033](https://github.com/PHPOffice/PhpSpreadsheet/pull/4033)
3737
- Conditional Range Unions and Intersections [Issue #4039](https://github.com/PHPOffice/PhpSpreadsheet/issues/4039) [PR #4042](https://github.com/PHPOffice/PhpSpreadsheet/pull/4042)
38+
- Propagate errors in Text functions. [Issue #2581](https://github.com/PHPOffice/PhpSpreadsheet/issues/2581) [PR #4080](https://github.com/PHPOffice/PhpSpreadsheet/pull/4080)
3839
- Csv Reader allow use of html mimetype. [Issue #4036](https://github.com/PHPOffice/PhpSpreadsheet/issues/4036) [PR #4040](https://github.com/PHPOffice/PhpSpreadsheet/pull/4040)
3940
- Problem rendering line chart with missing plot label. [PR #4074](https://github.com/PHPOffice/PhpSpreadsheet/pull/4074)
4041
- More RTL in Xlsx/Html Comments [Issue #4004](https://github.com/PHPOffice/PhpSpreadsheet/issues/4004) [PR #4065](https://github.com/PHPOffice/PhpSpreadsheet/pull/4065)

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4792,13 +4792,20 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
47924792

47934793
for ($row = 0; $row < $rows; ++$row) {
47944794
for ($column = 0; $column < $columns; ++$column) {
4795-
$operand1[$row][$column]
4796-
= Shared\StringHelper::substring(
4797-
self::boolToString($operand1[$row][$column])
4798-
. self::boolToString($operand2[$row][$column]),
4799-
0,
4800-
DataType::MAX_STRING_LENGTH
4801-
);
4795+
$op1x = self::boolToString($operand1[$row][$column]);
4796+
$op2x = self::boolToString($operand2[$row][$column]);
4797+
if (Information\ErrorValue::isError($op1x)) {
4798+
// no need to do anything
4799+
} elseif (Information\ErrorValue::isError($op2x)) {
4800+
$operand1[$row][$column] = $op2x;
4801+
} else {
4802+
$operand1[$row][$column]
4803+
= Shared\StringHelper::substring(
4804+
$op1x . $op2x,
4805+
0,
4806+
DataType::MAX_STRING_LENGTH
4807+
);
4808+
}
48024809
}
48034810
}
48044811
$result = $operand1;
@@ -4808,7 +4815,13 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
48084815
// using the concatenation operator
48094816
// with literals that fits in 32K,
48104817
// so I don't think we can overflow here.
4811-
$result = self::FORMULA_STRING_QUOTE . str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)) . self::FORMULA_STRING_QUOTE;
4818+
if (Information\ErrorValue::isError($operand1)) {
4819+
$result = $operand1;
4820+
} elseif (Information\ErrorValue::isError($operand2)) {
4821+
$result = $operand2;
4822+
} else {
4823+
$result = self::FORMULA_STRING_QUOTE . str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)) . self::FORMULA_STRING_QUOTE;
4824+
}
48124825
}
48134826
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result));
48144827
$stack->push('Value', $result);

src/PhpSpreadsheet/Calculation/Functions.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class Functions
2626
const RETURNDATE_PHP_DATETIME_OBJECT = 'O';
2727
const RETURNDATE_EXCEL = 'E';
2828

29+
public const NOT_YET_IMPLEMENTED = '#Not Yet Implemented';
30+
2931
/**
3032
* Compatibility mode to use for error checking and responses.
3133
*/
@@ -123,7 +125,7 @@ public static function getReturnDateType(): string
123125
*/
124126
public static function DUMMY(): string
125127
{
126-
return '#Not Yet Implemented';
128+
return self::NOT_YET_IMPLEMENTED;
127129
}
128130

129131
public static function isMatrixValue(mixed $idx): bool

src/PhpSpreadsheet/Calculation/Information/ErrorValue.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpSpreadsheet\Calculation\Information;
44

55
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
6+
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
67

78
class ErrorValue
89
{
@@ -35,7 +36,7 @@ public static function isErr(mixed $value = ''): array|bool
3536
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
3637
* with the same dimensions
3738
*/
38-
public static function isError(mixed $value = ''): array|bool
39+
public static function isError(mixed $value = '', bool $tryNotImplemented = false): array|bool
3940
{
4041
if (is_array($value)) {
4142
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
@@ -44,6 +45,9 @@ public static function isError(mixed $value = ''): array|bool
4445
if (!is_string($value)) {
4546
return false;
4647
}
48+
if ($tryNotImplemented && $value === Functions::NOT_YET_IMPLEMENTED) {
49+
return true;
50+
}
4751

4852
return in_array($value, ExcelError::ERROR_CODES, true);
4953
}

src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
44

55
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
6+
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
67
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
78

89
class CaseConvert
@@ -26,7 +27,11 @@ public static function lower(mixed $mixedCaseValue): array|string
2627
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
2728
}
2829

29-
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
30+
try {
31+
$mixedCaseValue = Helpers::extractString($mixedCaseValue, true);
32+
} catch (CalcExp $e) {
33+
return $e->getMessage();
34+
}
3035

3136
return StringHelper::strToLower($mixedCaseValue);
3237
}
@@ -48,7 +53,11 @@ public static function upper(mixed $mixedCaseValue): array|string
4853
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
4954
}
5055

51-
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
56+
try {
57+
$mixedCaseValue = Helpers::extractString($mixedCaseValue, true);
58+
} catch (CalcExp $e) {
59+
return $e->getMessage();
60+
}
5261

5362
return StringHelper::strToUpper($mixedCaseValue);
5463
}
@@ -70,7 +79,11 @@ public static function proper(mixed $mixedCaseValue): array|string
7079
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
7180
}
7281

73-
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
82+
try {
83+
$mixedCaseValue = Helpers::extractString($mixedCaseValue, true);
84+
} catch (CalcExp $e) {
85+
return $e->getMessage();
86+
}
7487

7588
return StringHelper::strToTitle($mixedCaseValue);
7689
}

src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
44

55
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
6+
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
67
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
78
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
89

@@ -26,7 +27,12 @@ public static function character(mixed $character): array|string
2627
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $character);
2728
}
2829

29-
$character = Helpers::validateInt($character);
30+
try {
31+
$character = Helpers::validateInt($character, true);
32+
} catch (CalcExp $e) {
33+
return $e->getMessage();
34+
}
35+
3036
$min = Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE ? 0 : 1;
3137
if ($character < $min || $character > 255) {
3238
return ExcelError::VALUE();
@@ -52,7 +58,12 @@ public static function code(mixed $characters): array|string|int
5258
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $characters);
5359
}
5460

55-
$characters = Helpers::extractString($characters);
61+
try {
62+
$characters = Helpers::extractString($characters, true);
63+
} catch (CalcExp $e) {
64+
return $e->getMessage();
65+
}
66+
5667
if ($characters === '') {
5768
return ExcelError::VALUE();
5869
}

src/PhpSpreadsheet/Calculation/TextData/Concatenate.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static function CONCATENATE(...$args): string
2727

2828
foreach ($aArgs as $arg) {
2929
$value = Helpers::extractString($arg);
30-
if (ErrorValue::isError($value)) {
30+
if (ErrorValue::isError($value, true)) {
3131
$returnValue = $value;
3232

3333
break;
@@ -85,7 +85,7 @@ private static function evaluateTextJoinArray(bool $ignoreEmpty, array &$aArgs):
8585
{
8686
foreach ($aArgs as $key => &$arg) {
8787
$value = Helpers::extractString($arg);
88-
if (ErrorValue::isError($value)) {
88+
if (ErrorValue::isError($value, true)) {
8989
return $value;
9090
}
9191

@@ -123,7 +123,7 @@ public static function builtinREPT(mixed $stringValue, mixed $repeatCount): arra
123123

124124
if (!is_numeric($repeatCount) || $repeatCount < 0) {
125125
$returnValue = ExcelError::VALUE();
126-
} elseif (ErrorValue::isError($stringValue)) {
126+
} elseif (ErrorValue::isError($stringValue, true)) {
127127
$returnValue = $stringValue;
128128
} else {
129129
$returnValue = str_repeat($stringValue, (int) $repeatCount);

src/PhpSpreadsheet/Calculation/TextData/Extract.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static function left(mixed $value, mixed $chars = 1): array|string
3131
}
3232

3333
try {
34-
$value = Helpers::extractString($value);
34+
$value = Helpers::extractString($value, true);
3535
$chars = Helpers::extractInt($chars, 0, 1);
3636
} catch (CalcExp $e) {
3737
return $e->getMessage();
@@ -61,7 +61,7 @@ public static function mid(mixed $value, mixed $start, mixed $chars): array|stri
6161
}
6262

6363
try {
64-
$value = Helpers::extractString($value);
64+
$value = Helpers::extractString($value, true);
6565
$start = Helpers::extractInt($start, 1);
6666
$chars = Helpers::extractInt($chars, 0);
6767
} catch (CalcExp $e) {
@@ -90,7 +90,7 @@ public static function right(mixed $value, mixed $chars = 1): array|string
9090
}
9191

9292
try {
93-
$value = Helpers::extractString($value);
93+
$value = Helpers::extractString($value, true);
9494
$chars = Helpers::extractInt($chars, 0, 1);
9595
} catch (CalcExp $e) {
9696
return $e->getMessage();
@@ -132,7 +132,13 @@ public static function before(mixed $text, $delimiter, mixed $instance = 1, mixe
132132
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
133133
}
134134

135-
$text = Helpers::extractString($text ?? '');
135+
try {
136+
$text = Helpers::extractString($text ?? '', true);
137+
Helpers::extractString(Functions::flattenSingleValue($delimiter ?? ''), true);
138+
} catch (CalcExp $e) {
139+
return $e->getMessage();
140+
}
141+
136142
$instance = (int) $instance;
137143
$matchMode = (int) $matchMode;
138144
$matchEnd = (int) $matchEnd;
@@ -190,7 +196,13 @@ public static function after(mixed $text, $delimiter, mixed $instance = 1, mixed
190196
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
191197
}
192198

193-
$text = Helpers::extractString($text ?? '');
199+
try {
200+
$text = Helpers::extractString($text ?? '', true);
201+
Helpers::extractString(Functions::flattenSingleValue($delimiter ?? ''), true);
202+
} catch (CalcExp $e) {
203+
return $e->getMessage();
204+
}
205+
194206
$instance = (int) $instance;
195207
$matchMode = (int) $matchMode;
196208
$matchEnd = (int) $matchEnd;

src/PhpSpreadsheet/Calculation/TextData/Format.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
99
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
1010
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
11+
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
1112
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
1213
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
1314
use PhpOffice\PhpSpreadsheet\RichText\RichText;
@@ -123,8 +124,13 @@ public static function TEXTFORMAT(mixed $value, mixed $format): array|string
123124
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format);
124125
}
125126

126-
$value = Helpers::extractString($value);
127-
$format = Helpers::extractString($format);
127+
try {
128+
$value = Helpers::extractString($value, true);
129+
$format = Helpers::extractString($format, true);
130+
} catch (CalcExp $e) {
131+
return $e->getMessage();
132+
}
133+
128134
$format = (string) NumberFormat::convertSystemFormats($format);
129135

130136
if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) {
@@ -152,6 +158,9 @@ private static function convertValue(mixed $value, bool $spacesMeanZero = false)
152158
}
153159
if (is_string($value)) {
154160
$value = trim($value);
161+
if (ErrorValue::isError($value, true)) {
162+
throw new CalcExp($value);
163+
}
155164
if ($spacesMeanZero && $value === '') {
156165
$value = 0;
157166
}
@@ -220,7 +229,7 @@ public static function VALUE(mixed $value = '')
220229
}
221230

222231
/**
223-
* TEXT.
232+
* VALUETOTEXT.
224233
*
225234
* @param mixed $value The value to format
226235
* Or can be an array of values

src/PhpSpreadsheet/Calculation/TextData/Helpers.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static function extractString(mixed $value, bool $throwIfError = false):
2727
if (is_bool($value)) {
2828
return self::convertBooleanValue($value);
2929
}
30-
if ($throwIfError && is_string($value) && ErrorValue::isError($value)) {
30+
if ($throwIfError && is_string($value) && ErrorValue::isError($value, true)) {
3131
throw new CalcExp($value);
3232
}
3333

@@ -63,18 +63,28 @@ public static function extractFloat(mixed $value): float
6363
$value = (float) $value;
6464
}
6565
if (!is_numeric($value)) {
66+
if (is_string($value) && ErrorValue::isError($value, true)) {
67+
throw new CalcExp($value);
68+
}
69+
6670
throw new CalcExp(ExcelError::VALUE());
6771
}
6872

6973
return (float) $value;
7074
}
7175

72-
public static function validateInt(mixed $value): int
76+
public static function validateInt(mixed $value, bool $throwIfError = false): int
7377
{
7478
if ($value === null) {
7579
$value = 0;
7680
} elseif (is_bool($value)) {
7781
$value = (int) $value;
82+
} elseif ($throwIfError && is_string($value) && !is_numeric($value)) {
83+
if (!ErrorValue::isError($value, true)) {
84+
$value = ExcelError::VALUE();
85+
}
86+
87+
throw new CalcExp($value);
7888
}
7989

8090
return (int) $value;

0 commit comments

Comments
 (0)