From b243f2f4e5a4768db3d33a2b4970e87265f92ddc Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 28 Nov 2025 13:21:27 -0800 Subject: [PATCH] CODE/UNICODE and CHAR/UNICHAR PhpSpreadsheet treats CODE and UNICODE as equivalent, likewise for CHAR and UNICHAR. They are, in fact, different. CODE and CHAR deal only with single-byte character sets (Windows-1252 or MacRoman), while UNICODE and UNICHAR deal with all of Unicode. This PR separates them. The existing unit test for CODE was, in many cases, applicable to UNICODE (for which there was no separate test). The tests are corrected for CODE, new tests are added, and a separate test for UNICODE is added. CHAR was mostly okay, new tests are added, and a separate test for UNICHAR is added. --- docs/references/function-list-by-category.md | 4 +- .../function-list-by-name-compact.md | 4 +- docs/references/function-list-by-name.md | 4 +- .../Calculation/FunctionArray.php | 4 +- .../Calculation/TextData/CharacterConvert.php | 75 +++++++++++++++---- .../Functions/TextData/CharTest.php | 31 ++++++++ .../Functions/TextData/CodeTest.php | 38 ++++++++++ .../Functions/TextData/OpenOfficeTest.php | 2 +- .../Functions/TextData/UnicharTest.php | 55 ++++++++++++++ .../Functions/TextData/UnicodeTest.php | 55 ++++++++++++++ tests/data/Calculation/TextData/CHAR.php | 42 ++++++++--- tests/data/Calculation/TextData/CODE.php | 28 +++++-- .../data/Calculation/TextData/OpenOffice.php | 9 ++- 13 files changed, 310 insertions(+), 41 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UnicharTest.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UnicodeTest.php diff --git a/docs/references/function-list-by-category.md b/docs/references/function-list-by-category.md index 7a8b8728d..c7f8f6bc9 100644 --- a/docs/references/function-list-by-category.md +++ b/docs/references/function-list-by-category.md @@ -569,8 +569,8 @@ THAINUMSOUND | **Not yet Implemented** THAINUMSTRING | **Not yet Implemented** THAISTRINGLENGTH | **Not yet Implemented** TRIM | \PhpOffice\PhpSpreadsheet\Calculation\TextData\Trim::spaces -UNICHAR | \PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert::character -UNICODE | \PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert::code +UNICHAR | \PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert::characterUnicode +UNICODE | \PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert::codeUnicode UPPER | \PhpOffice\PhpSpreadsheet\Calculation\TextData\CaseConvert::upper VALUE | \PhpOffice\PhpSpreadsheet\Calculation\TextData\Format::VALUE VALUETOTEXT | \PhpOffice\PhpSpreadsheet\Calculation\TextData\Format::valueToText diff --git a/docs/references/function-list-by-name-compact.md b/docs/references/function-list-by-name-compact.md index 6cb6508ee..ed3ec3721 100644 --- a/docs/references/function-list-by-name-compact.md +++ b/docs/references/function-list-by-name-compact.md @@ -599,8 +599,8 @@ TYPE | INFORMATION | Information\Value::type Excel Function | Category | PhpSpreadsheet Function -------------------------|-----------------------|-------------------------------------- -UNICHAR | TEXT_AND_DATA | TextData\CharacterConvert::character -UNICODE | TEXT_AND_DATA | TextData\CharacterConvert::code +UNICHAR | TEXT_AND_DATA | TextData\CharacterConvert::characterUnicode +UNICODE | TEXT_AND_DATA | TextData\CharacterConvert::codeUnicode UNIQUE | LOOKUP_AND_REFERENCE | LookupRef\Unique::unique UPPER | TEXT_AND_DATA | TextData\CaseConvert::upper USDOLLAR | FINANCIAL | Financial\Dollar::format diff --git a/docs/references/function-list-by-name.md b/docs/references/function-list-by-name.md index 3acf55887..8924d2a74 100644 --- a/docs/references/function-list-by-name.md +++ b/docs/references/function-list-by-name.md @@ -595,8 +595,8 @@ TYPE | CATEGORY_INFORMATION | \PhpOffice\PhpSpread Excel Function | Category | PhpSpreadsheet Function -------------------------|--------------------------------|-------------------------------------- -UNICHAR | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert::character -UNICODE | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert::code +UNICHAR | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert::characterUnicode +UNICODE | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert::codeUnicode UNIQUE | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Unique::unique UPPER | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData\CaseConvert::upper USDOLLAR | CATEGORY_FINANCIAL | \PhpOffice\PhpSpreadsheet\Calculation\Financial\Dollar::format diff --git a/src/PhpSpreadsheet/Calculation/FunctionArray.php b/src/PhpSpreadsheet/Calculation/FunctionArray.php index bc7f3a76d..25e0e9231 100644 --- a/src/PhpSpreadsheet/Calculation/FunctionArray.php +++ b/src/PhpSpreadsheet/Calculation/FunctionArray.php @@ -2496,12 +2496,12 @@ class FunctionArray extends CalculationBase ], 'UNICHAR' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData\CharacterConvert::class, 'character'], + 'functionCall' => [TextData\CharacterConvert::class, 'characterUnicode'], 'argumentCount' => '1', ], 'UNICODE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData\CharacterConvert::class, 'code'], + 'functionCall' => [TextData\CharacterConvert::class, 'codeUnicode'], 'argumentCount' => '1', ], 'UNIQUE' => [ diff --git a/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php b/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php index 2f15bfcd2..2086ad663 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php +++ b/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php @@ -6,11 +6,14 @@ use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; +use PhpOffice\PhpSpreadsheet\Shared\StringHelper; class CharacterConvert { use ArrayEnabled; + private static string $oneByteCharacterSet = 'Windows-1252'; + /** * CHAR. * @@ -27,6 +30,21 @@ class CharacterConvert return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $character); } + return self::characterBoth($character, true); + } + + /** @return array|string */ + public static function characterUnicode(mixed $character): array|string + { + if (is_array($character)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $character); + } + + return self::characterBoth($character, false); + } + + private static function characterBoth(mixed $character, bool $ansi = true): string + { try { $character = Helpers::validateInt($character, true); } catch (CalcExp $e) { @@ -34,12 +52,19 @@ class CharacterConvert } $min = Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE ? 0 : 1; - if ($character < $min || $character > 255) { + if ($character < $min || ($ansi && $character > 255) || $character > 0x10FFFF) { return ExcelError::VALUE(); } - $result = iconv('UCS-4LE', 'UTF-8', pack('V', $character)); + if ($character > 0x10FFFD) { // last assigned + return ExcelError::NA(); + } + if ($ansi) { + $result = chr($character); - return ($result === false) ? '' : $result; + return (string) iconv(self::$oneByteCharacterSet, 'UTF-8//IGNORE', $result); + } + + return mb_chr($character, 'UTF-8'); } /** @@ -57,7 +82,28 @@ class CharacterConvert if (is_array($characters)) { return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $characters); } + if (is_bool($characters) && Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + $characters = $characters ? '1' : '0'; + } + return self::codeBoth(StringHelper::convertToString($characters, convertBool: true), true); + } + + /** @return array|int|string */ + public static function codeUnicode(mixed $characters): array|string|int + { + if (is_array($characters)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $characters); + } + if (is_bool($characters) && Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + $characters = $characters ? '1' : '0'; + } + + return self::codeBoth(StringHelper::convertToString($characters, convertBool: true), false); + } + + private static function codeBoth(string $characters, bool $ansi = true): int|string + { try { $characters = Helpers::extractString($characters, true); } catch (CalcExp $e) { @@ -73,19 +119,22 @@ class CharacterConvert $character = mb_substr($characters, 0, 1, 'UTF-8'); } - return self::unicodeToOrd($character); + $result = mb_ord($character, 'UTF-8'); + if ($ansi && $result > 255) { + $result = iconv('UTF-8', self::$oneByteCharacterSet . '//IGNORE', $character); + + return ($result !== '') ? ord("$result") : 63; // question mark + } + + return $result; } - private static function unicodeToOrd(string $character): int + public static function setOneByteCharacterSet(string $characterSet): bool { - $retVal = 0; - $iconv = iconv('UTF-8', 'UCS-4LE', $character); - if ($iconv !== false) { - /** @var false|int[] */ - $result = unpack('V', $iconv); - if (is_array($result) && isset($result[1])) { - $retVal = $result[1]; - } + $retVal = false; + if ($characterSet === 'Windows-1252' || $characterSet === 'MACROMAN') { + self::$oneByteCharacterSet = $characterSet; + $retVal = true; } return $retVal; diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharTest.php index 7c9b80626..2b0a1cc5f 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharTest.php @@ -5,13 +5,44 @@ declare(strict_types=1); namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert as CC; use PHPUnit\Framework\Attributes\DataProvider; class CharTest extends AllSetupTeardown { + protected function tearDown(): void + { + parent::tearDown(); + CC::setOneByteCharacterSet('Windows-1252'); + } + #[DataProvider('providerCHAR')] public function testCHAR(mixed $expectedResult, mixed $character = 'omitted'): void { + // if espected is array, 1st is for char, 2nd for unichar + if (is_array($expectedResult)) { + $expectedResult = $expectedResult[0]; + } + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($character === 'omitted') { + $sheet->getCell('B1')->setValue('=CHAR()'); + } else { + $this->setCell('A1', $character); + $sheet->getCell('B1')->setValue('=CHAR(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + #[DataProvider('providerCHAR')] + public function testMacCHAR(mixed $expectedResult, mixed $character = 'omitted'): void + { + CC::setOneByteCharacterSet('MACROMAN'); + // if espected is array, 1st is for CHAR, 2nd UNICHAR, 3rd Mac CHAR + if (is_array($expectedResult)) { + $expectedResult = $expectedResult[2] ?? $expectedResult[0]; + } $this->mightHaveException($expectedResult); $sheet = $this->getSheet(); if ($character === 'omitted') { diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CodeTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CodeTest.php index 99f467f93..f0e14e910 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CodeTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CodeTest.php @@ -5,13 +5,44 @@ declare(strict_types=1); namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Calculation\TextData\CharacterConvert as CC; use PHPUnit\Framework\Attributes\DataProvider; class CodeTest extends AllSetupTeardown { + protected function tearDown(): void + { + parent::tearDown(); + CC::setOneByteCharacterSet('Windows-1252'); + } + #[DataProvider('providerCODE')] public function testCODE(mixed $expectedResult, mixed $character = 'omitted'): void { + // if espected is array, 1st is for code, 2nd for unicode + if (is_array($expectedResult)) { + $expectedResult = $expectedResult[0]; + } + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($character === 'omitted') { + $sheet->getCell('B1')->setValue('=CODE()'); + } else { + $this->setCell('A1', $character); + $sheet->getCell('B1')->setValue('=CODE(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + #[DataProvider('providerCODE')] + public function testMacCODE(mixed $expectedResult, mixed $character = 'omitted'): void + { + CC::setOneByteCharacterSet('MACROMAN'); + // if espected is array, 1st is for code, 2nd unicode, 3rd Mac CODE + if (is_array($expectedResult)) { + $expectedResult = $expectedResult[2] ?? $expectedResult[0]; + } $this->mightHaveException($expectedResult); $sheet = $this->getSheet(); if ($character === 'omitted') { @@ -48,4 +79,11 @@ class CodeTest extends AllSetupTeardown 'matrix' => [[[89, 111], [108, 111]], '{"Y", "o"; "l", "o"}'], ]; } + + public function testCharacterSet(): void + { + self::assertFalse(CC::setOneByteCharacterSet('UTF-8')); + self::assertTrue(CC::setOneByteCharacterSet('MACROMAN')); + self::assertTrue(CC::setOneByteCharacterSet('Windows-1252')); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/OpenOfficeTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/OpenOfficeTest.php index 5a73568b6..2bcad6ca7 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/OpenOfficeTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/OpenOfficeTest.php @@ -14,7 +14,7 @@ class OpenOfficeTest extends AllSetupTeardown $sheet = $this->getSheet(); $this->setCell('A1', $formula); $result = $sheet->getCell('A1')->getCalculatedValue(); - self::assertEquals($expectedResult, $result); + self::assertSame($expectedResult, $result); } public static function providerOpenOffice(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UnicharTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UnicharTest.php new file mode 100644 index 000000000..56ffe184b --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UnicharTest.php @@ -0,0 +1,55 @@ +mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($character === 'omitted') { + $sheet->getCell('B1')->setValue('=UNICHAR()'); + } else { + $this->setCell('A1', $character); + $sheet->getCell('B1')->setValue('=UNICHAR(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public static function providerCHAR(): array + { + return require 'tests/data/Calculation/TextData/CHAR.php'; + } + + /** @param mixed[] $expectedResult */ + #[DataProvider('providerCharArray')] + public function testCharArray(array $expectedResult, string $array): void + { + $calculation = Calculation::getInstance(); + + $formula = "=UNICHAR({$array})"; + $result = $calculation->calculateFormula($formula); + self::assertSame($expectedResult, $result); + } + + public static function providerCharArray(): array + { + return [ + 'row vector' => [[['P', 'H', 'P']], '{80, 72, 80}'], + 'column vector' => [[['P'], ['H'], ['P']], '{80; 72; 80}'], + 'matrix' => [[['Y', 'o'], ['l', 'o']], '{89, 111; 108, 111}'], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UnicodeTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UnicodeTest.php new file mode 100644 index 000000000..9bbd5ade4 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UnicodeTest.php @@ -0,0 +1,55 @@ +mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($character === 'omitted') { + $sheet->getCell('B1')->setValue('=UNICODE()'); + } else { + $this->setCell('A1', $character); + $sheet->getCell('B1')->setValue('=UNICODE(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public static function providerCODE(): array + { + return require 'tests/data/Calculation/TextData/CODE.php'; + } + + /** @param mixed[] $expectedResult */ + #[DataProvider('providerCodeArray')] + public function testCodeArray(array $expectedResult, string $array): void + { + $calculation = Calculation::getInstance(); + + $formula = "=UNICODE({$array})"; + $result = $calculation->calculateFormula($formula); + self::assertSame($expectedResult, $result); + } + + public static function providerCodeArray(): array + { + return [ + 'row vector' => [[[80, 72, 80]], '{"P", "H", "P"}'], + 'column vector' => [[[80], [72], [80]], '{"P"; "H"; "P"}'], + 'matrix' => [[[89, 111], [108, 111]], '{"Y", "o"; "l", "o"}'], + ]; + } +} diff --git a/tests/data/Calculation/TextData/CHAR.php b/tests/data/Calculation/TextData/CHAR.php index 400edd50d..bf5955db7 100644 --- a/tests/data/Calculation/TextData/CHAR.php +++ b/tests/data/Calculation/TextData/CHAR.php @@ -28,41 +28,65 @@ return [ 126, ], [ - 'Á', + ['Á', 'Á', '¡'], 193, ], [ - 'ÿ', + ['ÿ', 'ÿ', 'ˇ'], 255, ], [ - '#VALUE!', + ['#VALUE!', 'Ā'], 256, ], [ - '#VALUE!', // '⽇', + ['#VALUE!', '⽇'], 12103, ], [ - '#VALUE!', // 'œ', + ['#VALUE!', 'œ'], 0x153, ], [ - '#VALUE!', // 'ƒ', + ['#VALUE!', 'ƒ'], 0x192, ], [ - '#VALUE!', // '℅', + ['#VALUE!', '℅'], 0x2105, ], [ - '#VALUE!', // '∑', + ['#VALUE!', '∑'], 0x2211, ], [ - '#VALUE!', // '†', + ['#VALUE!', '†'], 0x2020, ], + 'example 1 different location all 3' => [ + ['†', mb_chr(134, 'UTF-8'), 'Ü'], + 134, + ], + 'example 2 different location all 3' => [ + ['€', mb_chr(128, 'UTF-8'), 'Ä'], + 128, + ], + 'non-ascii same win-1252 vs unicode, different mac' => [ + ['Ð', 'Ð', '–'], + 0xD0, + ], + 'last assigned Unicode character' => [ + ['#VALUE!', mb_chr(0x10FFFD, 'UTF-8')], + 0x10FFFD, + ], + 'highest possible code point' => [ + ['#VALUE!', '#N/A'], + 0x10FFFF, + ], + 'above highest possible code point' => [ + '#VALUE!', + 0x110000, + ], 'omitted argument' => ['exception'], 'non-printable' => ["\x02", 2], 'bool argument' => ["\x01", true], diff --git a/tests/data/Calculation/TextData/CODE.php b/tests/data/Calculation/TextData/CODE.php index 62bd213b2..3d5dd303e 100644 --- a/tests/data/Calculation/TextData/CODE.php +++ b/tests/data/Calculation/TextData/CODE.php @@ -2,6 +2,10 @@ declare(strict_types=1); +// Used to test both CODE and UNICODE. +// If expected result is array, 1st entry is for CODE, 2nd for UNICODE, +// and 3rd for CODE using MACROMAN. + return [ [ '#VALUE!', @@ -48,28 +52,40 @@ return [ '£125.00', ], [ - 12103, + [63, 12103], '⽇', ], [ - 0x153, + [156, 0x153, 207], 'œ', ], [ - 0x192, + [131, 0x192, 196], 'ƒ', ], [ - 0x2105, + [63, 0x2105], '℅', ], [ - 0x2211, + [63, 0x2211, 183], '∑', ], [ - 0x2020, + [134, 0x2020, 160], '†', ], + [ + [128, 8364, 63], + '€', + ], + 'non-ascii but same win-1252 vs unicode' => [ + 0xD0, + 'Ð', + ], + 'ascii control character' => [ + 2, + "\x02", + ], 'omitted argument' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/OpenOffice.php b/tests/data/Calculation/TextData/OpenOffice.php index 5c1c4a035..7618300a2 100644 --- a/tests/data/Calculation/TextData/OpenOffice.php +++ b/tests/data/Calculation/TextData/OpenOffice.php @@ -4,11 +4,12 @@ declare(strict_types=1); return [ 'OO allows CHAR(0)' => ["\x00", '=CHAR(0)'], - 'OO treats CODE(bool) as 0/1' => ['48', '=CODE(FALSE)'], + 'OO treats CODE(bool) as 0/1' => [48, '=CODE(FALSE)'], + 'OO treats UNICODE(bool) as 0/1' => [49, '=UNICODE(TRUE)'], 'OO treats bool as string as 0/1 to REPT' => ['111', '=REPT(true, 3)'], 'OO treats bool as string as 0/1 to CLEAN' => ['0', '=CLEAN(false)'], 'OO treats bool as string as 0/1 to TRIM' => ['1', '=TRIM(true)'], - 'OO treats bool as string as 0/1 to LEN' => ['1', '=LEN(false)'], + 'OO treats bool as string as 0/1 to LEN' => [1, '=LEN(false)'], 'OO treats bool as string as 0/1 to EXACT parm 1' => [true, '=EXACT(true, 1)'], 'OO treats bool as string as 0/1 to EXACT parm 2' => [true, '=EXACT(0, false)'], 'OO treats bool as string as 0/1 to FIND parm 1' => [2, '=FIND(true, "210")'], @@ -24,7 +25,7 @@ return [ 'OO treats false as 0 SUBSTITUTE parm 1' => ['6', '=SUBSTITUTE(true, "1", "6")'], 'OO treats true as 1 SUBSTITUTE parm 4' => ['zbcade', '=SUBSTITUTE("abcade", "a", "z", true)'], 'OO TEXT boolean in lieu of string' => ['0', '=TEXT(false, "@")'], - 'OO VALUE boolean in lieu of string' => ['0', '=VALUE(false)'], - 'OO NUMBERVALUE boolean in lieu of string' => ['1', '=NUMBERVALUE(true)'], + 'OO VALUE boolean in lieu of string' => [0.0, '=VALUE(false)'], + 'OO NUMBERVALUE boolean in lieu of string' => [1.0, '=NUMBERVALUE(true)'], 'OO TEXTJOIN boolean in lieu of string' => ['1-0-1', '=TEXTJOIN("-", true, true, false, true)'], ];