From 6ae2a897f2164e0087c27075d0e6ea8046f3b7cf Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Wed, 9 Jun 2021 23:11:32 -0700 Subject: [PATCH] TextData - Minor Changes, Test Coverage Per agreement on a previous push, I looked into standardizing the initialization of the TextData functions (like Engineering and MathTrig), with particular regard for avoiding multiple later null coercions. This simplifies the code quite a bit. This PR also increases coverage to 100% for all TextData modules. All entries in Phpstan baseline for non-deprecated TEXTDATA functions are removed. There were some minor bugfixes. Whereas Excel (and Gnumeric) treat booleans when supplied as strings as 'TRUE' or 'FALSE', ODS treats them as '1' or '0'. Unlike Excel, ODS generally does not allow bool for int arguments; it does, however, allow them for FIND and SEARCH. ODS allows boolean for into for SUBSTITUTE even though Excel doesn't. ODS allows bool for string for NUMBERVALUE and VALUE even though Excel doesn't. ODS accepts 0 as an argument for CHAR; Excel doesn't. Most of this seems like random decisions on the part of the developers; I've done my best to follow the products in each case. There is a new test member devoted to ODS tests. Gnumeric has an anomaly vis-a-vis the others - if length is supplied to LEFT/MID/RIGHT as null, Gnumeric treats it as 0 rather than 1. All tests now take place in the context of a spreadsheet ... Except for RETURNSTRING, which is not the implementation of an Excel function, and is referred to in the rest of PhpSpreadsheet only in the unit tests for itself. It should probably be deprecated, but that is not part of this PR, just in case there is some reason for it that I couldn't discern. I have tried to make the first line of each doc block identify the Excel function name rather than its name in PhpSpreadsheet. I think it makes things more comprehensible. Some tests call Settings::setLocale, but there was no Settings::getLocale. At the end of the tests which do it, they invoke setLocale('EN-US'), which, in a practical sense, is sufficient. However, in theory it would be better for them to get the current locale before changing it, then changing it back to the original when the time came. I have added getLocale and made the appropriate testing change. The CHAR function took an interesting turn. One can set the value of a cell to, say, CHAR(2), the ASCII/UTF-8 representation of a control character, which is not legal in certain contexts. The only Reader/Writer that could handle this without problems is Xls, which deals with binary data all the time. However, if you tried to write it to Xlsx, Excel would not be able to open the resulting file because of what it considers an illegal character. I changed the Xlsx writer to escape such characters when writing the value of a string function. I did not make any other changes to the Xlsx writer - it seems to me that setting a cell to CHAR(2) is legitimate, but setting it to say `"\x02"` seems less likely to be legitimate, so the latter will still fail (although `="\x02"` should work). The Xlsx reader already supports the escape mechanism that I added to the writer. CHAR control character and Ods - not supported by either Reader or Writer. I did not attempt to add this now. There is lots still missing from ODS, and this item just can't be a high priority amongst all of those. CHAR control character and Csv - it is supported by reader and writer if the file has a csv extension. However, trying to guess the mime type without an extension - the control character makes mime_get_type guess application/octet-stream, and PhpSpreadsheet therefore thinks that Csv can't read it. CHAR control character and Html. Actual use of the control character in the file is subject to the same problems as Xml (i.e. Xlsx and Ods). It wasn't terribly difficult to get the Html Writer to change `"\x02"` to "``". I believe that this is technically legal; however, DOMDocument.loadHTML rejects it as an illegal entity, and I am not convinced that it is wrong to do so, so I haven't changed the Html writer. --- phpstan-baseline.neon | 95 ---------- .../Calculation/TextData/CaseConvert.php | 16 +- .../Calculation/TextData/CharacterConvert.php | 44 ++--- .../Calculation/TextData/Concatenate.php | 27 +-- .../Calculation/TextData/Extract.php | 59 +++---- .../Calculation/TextData/Format.php | 100 +++++++---- .../Calculation/TextData/Helpers.php | 90 ++++++++++ .../Calculation/TextData/Replace.php | 44 +++-- .../Calculation/TextData/Search.php | 62 +++---- .../Calculation/TextData/Text.php | 17 +- .../Calculation/TextData/Trim.php | 38 +--- src/PhpSpreadsheet/Settings.php | 5 + src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 1 + .../Functions/TextData/AllSetupTeardown.php | 105 +++++++++++ .../TextData/CharNonPrintableTest.php | 47 +++++ .../Functions/TextData/CharTest.php | 17 +- .../Functions/TextData/CleanTest.php | 17 +- .../Functions/TextData/CodeTest.php | 17 +- .../Functions/TextData/ConcatenateTest.php | 18 +- .../Functions/TextData/DeprecatedTest.php | 44 +++++ .../Functions/TextData/DollarTest.php | 23 ++- .../Functions/TextData/ExactTest.php | 26 ++- .../Functions/TextData/FindTest.php | 29 ++- .../Functions/TextData/FixedTest.php | 29 ++- .../Functions/TextData/LeftTest.php | 144 +++++++++++++-- .../Functions/TextData/LenTest.php | 19 +- .../Functions/TextData/LowerTest.php | 32 ++-- .../Functions/TextData/MidTest.php | 166 ++++++++++++++++-- .../Functions/TextData/NumberValueTest.php | 29 ++- .../Functions/TextData/OpenOfficeTest.php | 26 +++ .../Functions/TextData/ProperTest.php | 32 ++-- .../Functions/TextData/ReplaceTest.php | 36 +++- .../Functions/TextData/ReptTest.php | 48 ++--- .../Functions/TextData/RightTest.php | 144 +++++++++++++-- .../Functions/TextData/SearchTest.php | 29 ++- .../Functions/TextData/SubstituteTest.php | 36 +++- .../Functions/TextData/TextJoinTest.php | 20 ++- .../Functions/TextData/TextTest.php | 23 ++- .../Functions/TextData/TrimTest.php | 17 +- .../Functions/TextData/UpperTest.php | 32 ++-- .../Functions/TextData/ValueTest.php | 18 +- tests/data/Calculation/TextData/CHAR.php | 6 + tests/data/Calculation/TextData/CLEAN.php | 1 + tests/data/Calculation/TextData/CODE.php | 1 + .../data/Calculation/TextData/CONCATENATE.php | 1 + tests/data/Calculation/TextData/DOLLAR.php | 6 + tests/data/Calculation/TextData/EXACT.php | 2 + tests/data/Calculation/TextData/FIND.php | 2 + tests/data/Calculation/TextData/FIXED.php | 5 + tests/data/Calculation/TextData/LEFT.php | 11 +- tests/data/Calculation/TextData/LEN.php | 1 + tests/data/Calculation/TextData/LOWER.php | 1 + tests/data/Calculation/TextData/MID.php | 17 +- .../data/Calculation/TextData/NUMBERVALUE.php | 26 +-- .../data/Calculation/TextData/OpenOffice.php | 28 +++ tests/data/Calculation/TextData/PROPER.php | 1 + tests/data/Calculation/TextData/REPLACE.php | 8 + tests/data/Calculation/TextData/REPT.php | 16 +- tests/data/Calculation/TextData/RIGHT.php | 11 +- tests/data/Calculation/TextData/SEARCH.php | 2 + .../data/Calculation/TextData/SUBSTITUTE.php | 12 ++ tests/data/Calculation/TextData/TEXT.php | 3 + tests/data/Calculation/TextData/TEXTJOIN.php | 5 + tests/data/Calculation/TextData/TRIM.php | 1 + tests/data/Calculation/TextData/UPPER.php | 1 + tests/data/Calculation/TextData/VALUE.php | 3 + 66 files changed, 1429 insertions(+), 563 deletions(-) create mode 100644 src/PhpSpreadsheet/Calculation/TextData/Helpers.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/TextData/AllSetupTeardown.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharNonPrintableTest.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/TextData/DeprecatedTest.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/TextData/OpenOfficeTest.php create mode 100644 tests/data/Calculation/TextData/OpenOffice.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4ee0a9efd3..746f1d586d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1320,11 +1320,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Calculation/TextData.php - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:TRIMSPACES\\(\\) should return string but returns string\\|null\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:CONCATENATE\\(\\) has parameter \\$args with no typehint specified\\.$#" count: 1 @@ -1340,96 +1335,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Calculation/TextData.php - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:character\\(\\) should return string but returns string\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:unicodeToOrd\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:unicodeToOrd\\(\\) has parameter \\$character with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php - - - - message: "#^Cannot access offset 1 on array\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php - - - - message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:convertBooleanValue\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:convertBooleanValue\\(\\) has parameter \\$value with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Concatenate\\:\\:CONCATENATE\\(\\) has parameter \\$args with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Concatenate\\:\\:convertBooleanValue\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Concatenate\\:\\:convertBooleanValue\\(\\) has parameter \\$value with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php - - - - message: "#^Parameter \\#3 \\$length of function mb_substr expects int\\|null, float\\|int\\<0, max\\>\\|string given\\.$#" - count: 3 - path: src/PhpSpreadsheet/Calculation/TextData/Extract.php - - - - message: "#^Parameter \\#2 \\$start of function mb_substr expects int, float\\|int given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Calculation/TextData/Extract.php - - - - message: "#^Cannot cast array\\|float\\|int\\|string to float\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/Format.php - - - - message: "#^Parameter \\#3 \\$offset of function mb_strpos expects int, float\\|int given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/Search.php - - - - message: "#^Parameter \\#3 \\$offset of function mb_stripos expects int, float\\|int given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/Search.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Trim\\:\\:\\$invalidChars has no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/Trim.php - - - - message: "#^Parameter \\#1 \\$str of function trim expects string, float\\|int\\|string given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Calculation/TextData/Trim.php - - - - message: "#^Parameter \\#1 \\$str of function trim expects string, string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/TextData/Trim.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has no return typehint specified\\.$#" count: 1 diff --git a/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php b/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php index 36b5efbd6c..664cc2d883 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php +++ b/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php @@ -2,7 +2,6 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; @@ -18,10 +17,7 @@ class CaseConvert public static function lower($mixedCaseValue): string { $mixedCaseValue = Functions::flattenSingleValue($mixedCaseValue); - - if (is_bool($mixedCaseValue)) { - $mixedCaseValue = ($mixedCaseValue === true) ? Calculation::getTRUE() : Calculation::getFALSE(); - } + $mixedCaseValue = Helpers::extractString($mixedCaseValue); return StringHelper::strToLower($mixedCaseValue); } @@ -36,10 +32,7 @@ public static function lower($mixedCaseValue): string public static function upper($mixedCaseValue): string { $mixedCaseValue = Functions::flattenSingleValue($mixedCaseValue); - - if (is_bool($mixedCaseValue)) { - $mixedCaseValue = ($mixedCaseValue === true) ? Calculation::getTRUE() : Calculation::getFALSE(); - } + $mixedCaseValue = Helpers::extractString($mixedCaseValue); return StringHelper::strToUpper($mixedCaseValue); } @@ -54,10 +47,7 @@ public static function upper($mixedCaseValue): string public static function proper($mixedCaseValue): string { $mixedCaseValue = Functions::flattenSingleValue($mixedCaseValue); - - if (is_bool($mixedCaseValue)) { - $mixedCaseValue = ($mixedCaseValue === true) ? Calculation::getTRUE() : Calculation::getFALSE(); - } + $mixedCaseValue = Helpers::extractString($mixedCaseValue); return StringHelper::strToTitle($mixedCaseValue); } diff --git a/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php b/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php index 4397b538b5..0aca57e6ce 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php +++ b/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php @@ -2,34 +2,29 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Functions; class CharacterConvert { /** - * CHARACTER. + * CHAR. * * @param mixed $character Integer Value to convert to its character representation */ public static function character($character): string { - $character = Functions::flattenSingleValue($character); - - if (!is_numeric($character)) { - return Functions::VALUE(); - } - - $character = (int) $character; - if ($character < 1 || $character > 255) { + $character = Helpers::validateInt($character); + $min = Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE ? 0 : 1; + if ($character < $min || $character > 255) { return Functions::VALUE(); } + $result = iconv('UCS-4LE', 'UTF-8', pack('V', $character)); - return iconv('UCS-4LE', 'UTF-8', pack('V', $character)); + return ($result === false) ? '' : $result; } /** - * ASCIICODE. + * CODE. * * @param mixed $characters String character to convert to its ASCII value * @@ -37,13 +32,10 @@ public static function character($character): string */ public static function code($characters) { - if (($characters === null) || ($characters === '')) { + $characters = Helpers::extractString($characters); + if ($characters === '') { return Functions::VALUE(); } - $characters = Functions::flattenSingleValue($characters); - if (is_bool($characters)) { - $characters = self::convertBooleanValue($characters); - } $character = $characters; if (mb_strlen($characters, 'UTF-8') > 1) { @@ -53,17 +45,17 @@ public static function code($characters) return self::unicodeToOrd($character); } - private static function unicodeToOrd($character) - { - return unpack('V', iconv('UTF-8', 'UCS-4LE', $character))[1]; - } - - private static function convertBooleanValue($value) + private static function unicodeToOrd(string $character): int { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - return (int) $value; + $retVal = 0; + $iconv = iconv('UTF-8', 'UCS-4LE', $character); + if ($iconv !== false) { + $result = unpack('V', $iconv); + if (is_array($result) && isset($result[1])) { + $retVal = $result[1]; + } } - return ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); + return $retVal; } } diff --git a/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php b/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php index 5780bb6e26..d53fc82289 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php @@ -2,13 +2,14 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Functions; class Concatenate { /** * CONCATENATE. + * + * @param array $args */ public static function CONCATENATE(...$args): string { @@ -16,11 +17,9 @@ public static function CONCATENATE(...$args): string // Loop through arguments $aArgs = Functions::flattenArray($args); + foreach ($aArgs as $arg) { - if (is_bool($arg)) { - $arg = self::convertBooleanValue($arg); - } - $returnValue .= $arg; + $returnValue .= Helpers::extractString($arg); } return $returnValue; @@ -35,13 +34,15 @@ public static function CONCATENATE(...$args): string */ public static function TEXTJOIN($delimiter, $ignoreEmpty, ...$args): string { + $delimiter = Functions::flattenSingleValue($delimiter); + $ignoreEmpty = Functions::flattenSingleValue($ignoreEmpty); // Loop through arguments $aArgs = Functions::flattenArray($args); foreach ($aArgs as $key => &$arg) { if ($ignoreEmpty === true && is_string($arg) && trim($arg) === '') { unset($aArgs[$key]); } elseif (is_bool($arg)) { - $arg = self::convertBooleanValue($arg); + $arg = Helpers::convertBooleanValue($arg); } } @@ -59,24 +60,12 @@ public static function TEXTJOIN($delimiter, $ignoreEmpty, ...$args): string public static function builtinREPT($stringValue, $repeatCount): string { $repeatCount = Functions::flattenSingleValue($repeatCount); + $stringValue = Helpers::extractString($stringValue); if (!is_numeric($repeatCount) || $repeatCount < 0) { return Functions::VALUE(); } - if (is_bool($stringValue)) { - $stringValue = self::convertBooleanValue($stringValue); - } - return str_repeat($stringValue, (int) $repeatCount); } - - private static function convertBooleanValue($value) - { - if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { - return (int) $value; - } - - return ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } } diff --git a/src/PhpSpreadsheet/Calculation/TextData/Extract.php b/src/PhpSpreadsheet/Calculation/TextData/Extract.php index 015fabfb84..3dd281f28f 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Extract.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Extract.php @@ -2,8 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; class Extract { @@ -13,20 +12,16 @@ class Extract * @param mixed $value String value from which to extract characters * @param mixed $chars The number of characters to extract (as an integer) */ - public static function left($value = '', $chars = 1): string + public static function left($value, $chars = 1): string { - $value = Functions::flattenSingleValue($value); - $chars = Functions::flattenSingleValue($chars); - - if (!is_numeric($chars) || $chars < 0) { - return Functions::VALUE(); - } - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); + try { + $value = Helpers::extractString($value); + $chars = Helpers::extractInt($chars, 0, 1); + } catch (CalcExp $e) { + return $e->getMessage(); } - return mb_substr($value ?? '', 0, $chars, 'UTF-8'); + return mb_substr($value, 0, $chars, 'UTF-8'); } /** @@ -36,21 +31,17 @@ public static function left($value = '', $chars = 1): string * @param mixed $start Integer offset of the first character that we want to extract * @param mixed $chars The number of characters to extract (as an integer) */ - public static function mid($value = '', $start = 1, $chars = null): string + public static function mid($value, $start, $chars): string { - $value = Functions::flattenSingleValue($value); - $start = Functions::flattenSingleValue($start); - $chars = Functions::flattenSingleValue($chars); - - if (!is_numeric($start) || $start < 1 || !is_numeric($chars) || $chars < 0) { - return Functions::VALUE(); + try { + $value = Helpers::extractString($value); + $start = Helpers::extractInt($start, 1); + $chars = Helpers::extractInt($chars, 0); + } catch (CalcExp $e) { + return $e->getMessage(); } - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return mb_substr($value ?? '', --$start, $chars, 'UTF-8'); + return mb_substr($value, --$start, $chars, 'UTF-8'); } /** @@ -59,19 +50,15 @@ public static function mid($value = '', $start = 1, $chars = null): string * @param mixed $value String value from which to extract characters * @param mixed $chars The number of characters to extract (as an integer) */ - public static function right($value = '', $chars = 1): string + public static function right($value, $chars = 1): string { - $value = Functions::flattenSingleValue($value); - $chars = Functions::flattenSingleValue($chars); - - if (!is_numeric($chars) || $chars < 0) { - return Functions::VALUE(); - } - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); + try { + $value = Helpers::extractString($value); + $chars = Helpers::extractInt($chars, 0, 1); + } catch (CalcExp $e) { + return $e->getMessage(); } - return mb_substr($value ?? '', mb_strlen($value ?? '', 'UTF-8') - $chars, $chars, 'UTF-8'); + return mb_substr($value, mb_strlen($value, 'UTF-8') - $chars, $chars, 'UTF-8'); } } diff --git a/src/PhpSpreadsheet/Calculation/TextData/Format.php b/src/PhpSpreadsheet/Calculation/TextData/Format.php index 6bad17441a..3286de0cb0 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Format.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Format.php @@ -4,6 +4,7 @@ use DateTimeInterface; use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; use PhpOffice\PhpSpreadsheet\Shared\Date; @@ -25,14 +26,12 @@ class Format */ public static function DOLLAR($value = 0, $decimals = 2): string { - $value = Functions::flattenSingleValue($value); - $decimals = $decimals === null ? 2 : Functions::flattenSingleValue($decimals); - - // Validate parameters - if (!is_numeric($value) || !is_numeric($decimals)) { - return Functions::VALUE(); + try { + $value = Helpers::extractFloat($value); + $decimals = Helpers::extractInt($decimals, -100, 0, true); + } catch (CalcExp $e) { + return $e->getMessage(); } - $decimals = (int) $decimals; $mask = '$#,##0'; if ($decimals > 0) { @@ -50,7 +49,7 @@ public static function DOLLAR($value = 0, $decimals = 2): string } /** - * FIXEDFORMAT. + * FIXED. * * @param mixed $value The value to format * @param mixed $decimals Integer value for the number of decimal places that should be formatted @@ -58,17 +57,13 @@ public static function DOLLAR($value = 0, $decimals = 2): string */ public static function FIXEDFORMAT($value, $decimals = 2, $noCommas = false): string { - $value = Functions::flattenSingleValue($value); - $decimals = $decimals === null ? 2 : Functions::flattenSingleValue($decimals); - $noCommas = Functions::flattenSingleValue($noCommas); - - // Validate parameters - if (!is_numeric($value) || !is_numeric($decimals)) { - return Functions::VALUE(); + try { + $value = Helpers::extractFloat($value); + $decimals = Helpers::extractInt($decimals, -100, 0, true); + $noCommas = Functions::flattenSingleValue($noCommas); + } catch (CalcExp $e) { + return $e->getMessage(); } - $decimals = (float) $decimals; - $value = (float) $value; - $decimals = (int) floor($decimals); $valueResult = round($value, $decimals); if ($decimals < 0) { @@ -87,23 +82,42 @@ public static function FIXEDFORMAT($value, $decimals = 2, $noCommas = false): st } /** - * TEXTFORMAT. + * TEXT. * * @param mixed $value The value to format * @param mixed $format A string with the Format mask that should be used */ public static function TEXTFORMAT($value, $format): string { - $value = Functions::flattenSingleValue($value); - $format = Functions::flattenSingleValue($format); + $value = Helpers::extractString($value); + $format = Helpers::extractString($format); - if ((is_string($value)) && (!is_numeric($value)) && Date::isDateTimeFormatCode($format)) { + if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) { $value = DateTimeExcel\DateValue::fromString($value); } return (string) NumberFormat::toFormattedString($value, $format); } + /** + * @param mixed $value Value to check + * + * @return mixed + */ + private static function convertValue($value) + { + $value = ($value === null) ? 0 : Functions::flattenSingleValue($value); + if (is_bool($value)) { + if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + $value = (int) $value; + } else { + throw new CalcExp(Functions::VALUE()); + } + } + + return $value; + } + /** * VALUE. * @@ -113,8 +127,11 @@ public static function TEXTFORMAT($value, $format): string */ public static function VALUE($value = '') { - $value = Functions::flattenSingleValue($value); - + try { + $value = self::convertValue($value); + } catch (CalcExp $e) { + return $e->getMessage(); + } if (!is_numeric($value)) { $numberValue = str_replace( StringHelper::getThousandsSeparator(), @@ -150,6 +167,26 @@ public static function VALUE($value = '') return (float) $value; } + /** + * @param mixed $decimalSeparator + */ + private static function getDecimalSeparator($decimalSeparator): string + { + $decimalSeparator = Functions::flattenSingleValue($decimalSeparator); + + return empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : (string) $decimalSeparator; + } + + /** + * @param mixed $groupSeparator + */ + private static function getGroupSeparator($groupSeparator): string + { + $groupSeparator = Functions::flattenSingleValue($groupSeparator); + + return empty($groupSeparator) ? StringHelper::getThousandsSeparator() : (string) $groupSeparator; + } + /** * NUMBERVALUE. * @@ -161,14 +198,15 @@ public static function VALUE($value = '') */ public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null) { - $value = Functions::flattenSingleValue($value); - $decimalSeparator = Functions::flattenSingleValue($decimalSeparator); - $groupSeparator = Functions::flattenSingleValue($groupSeparator); + try { + $value = self::convertValue($value); + $decimalSeparator = self::getDecimalSeparator($decimalSeparator); + $groupSeparator = self::getGroupSeparator($groupSeparator); + } catch (CalcExp $e) { + return $e->getMessage(); + } if (!is_numeric($value)) { - $decimalSeparator = empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : $decimalSeparator; - $groupSeparator = empty($groupSeparator) ? StringHelper::getThousandsSeparator() : $groupSeparator; - $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE); if ($decimalPositions > 1) { return Functions::VALUE(); @@ -193,6 +231,6 @@ public static function NUMBERVALUE($value = '', $decimalSeparator = null, $group } } - return (float) $value; + return is_array($value) ? Functions::VALUE() : (float) $value; } } diff --git a/src/PhpSpreadsheet/Calculation/TextData/Helpers.php b/src/PhpSpreadsheet/Calculation/TextData/Helpers.php new file mode 100644 index 0000000000..623bf32b4a --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/TextData/Helpers.php @@ -0,0 +1,90 @@ +getMessage(); + } return $left . $newText . $right; } @@ -35,15 +41,25 @@ public static function replace($oldText, $start, $chars, $newText): string * @param mixed $toText The string value that we want to replace with in $text * @param mixed $instance Integer instance Number for the occurrence of frmText to change */ - public static function substitute($text = '', $fromText = '', $toText = '', $instance = 0): string + public static function substitute($text = '', $fromText = '', $toText = '', $instance = null): string { - $text = Functions::flattenSingleValue($text); - $fromText = Functions::flattenSingleValue($fromText); - $toText = Functions::flattenSingleValue($toText); - $instance = floor(Functions::flattenSingleValue($instance)); - - if ($instance == 0) { - return str_replace($fromText, $toText, $text); + try { + $text = Helpers::extractString($text); + $fromText = Helpers::extractString($fromText); + $toText = Helpers::extractString($toText); + $instance = Functions::flattenSingleValue($instance); + if ($instance === null) { + return str_replace($fromText, $toText, $text); + } + if (is_bool($instance)) { + if ($instance === false || Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) { + return Functions::Value(); + } + $instance = 1; + } + $instance = Helpers::extractInt($instance, 1, 0, true); + } catch (CalcExp $e) { + return $e->getMessage(); } $pos = -1; diff --git a/src/PhpSpreadsheet/Calculation/TextData/Search.php b/src/PhpSpreadsheet/Calculation/TextData/Search.php index 2da688d884..c9eed2e5a5 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Search.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Search.php @@ -2,14 +2,14 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; class Search { /** - * SEARCHSENSITIVE. + * FIND (case sensitive search). * * @param mixed $needle The string to look for * @param mixed $haystack The string in which to look @@ -19,24 +19,22 @@ class Search */ public static function sensitive($needle, $haystack, $offset = 1) { - $needle = Functions::flattenSingleValue($needle); - $haystack = Functions::flattenSingleValue($haystack); - $offset = Functions::flattenSingleValue($offset); + try { + $needle = Helpers::extractString($needle); + $haystack = Helpers::extractString($haystack); + $offset = Helpers::extractInt($offset, 1, 0, true); + } catch (CalcExp $e) { + return $e->getMessage(); + } - if (!is_bool($needle)) { - if (is_bool($haystack)) { - $haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE(); + if (StringHelper::countCharacters($haystack) >= $offset) { + if (StringHelper::countCharacters($needle) === 0) { + return $offset; } - if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) { - if (StringHelper::countCharacters($needle) === 0) { - return $offset; - } - - $pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8'); - if ($pos !== false) { - return ++$pos; - } + $pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8'); + if ($pos !== false) { + return ++$pos; } } @@ -44,7 +42,7 @@ public static function sensitive($needle, $haystack, $offset = 1) } /** - * SEARCHINSENSITIVE. + * SEARCH (case insensitive search). * * @param mixed $needle The string to look for * @param mixed $haystack The string in which to look @@ -54,24 +52,22 @@ public static function sensitive($needle, $haystack, $offset = 1) */ public static function insensitive($needle, $haystack, $offset = 1) { - $needle = Functions::flattenSingleValue($needle); - $haystack = Functions::flattenSingleValue($haystack); - $offset = Functions::flattenSingleValue($offset); + try { + $needle = Helpers::extractString($needle); + $haystack = Helpers::extractString($haystack); + $offset = Helpers::extractInt($offset, 1, 0, true); + } catch (CalcExp $e) { + return $e->getMessage(); + } - if (!is_bool($needle)) { - if (is_bool($haystack)) { - $haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE(); + if (StringHelper::countCharacters($haystack) >= $offset) { + if (StringHelper::countCharacters($needle) === 0) { + return $offset; } - if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) { - if (StringHelper::countCharacters($needle) === 0) { - return $offset; - } - - $pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8'); - if ($pos !== false) { - return ++$pos; - } + $pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8'); + if ($pos !== false) { + return ++$pos; } } diff --git a/src/PhpSpreadsheet/Calculation/TextData/Text.php b/src/PhpSpreadsheet/Calculation/TextData/Text.php index f3da4bb339..dda0baa58a 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Text.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Text.php @@ -2,25 +2,20 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Functions; class Text { /** - * STRINGLENGTH. + * LEN. * * @param mixed $value String Value */ public static function length($value = ''): int { - $value = Functions::flattenSingleValue($value); + $value = Helpers::extractString($value); - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return mb_strlen($value ?? '', 'UTF-8'); + return mb_strlen($value, 'UTF-8'); } /** @@ -33,10 +28,10 @@ public static function length($value = ''): int */ public static function exact($value1, $value2): bool { - $value1 = Functions::flattenSingleValue($value1); - $value2 = Functions::flattenSingleValue($value2); + $value1 = Helpers::extractString($value1); + $value2 = Helpers::extractString($value2); - return ((string) $value2) === ((string) $value1); + return $value2 === $value1; } /** diff --git a/src/PhpSpreadsheet/Calculation/TextData/Trim.php b/src/PhpSpreadsheet/Calculation/TextData/Trim.php index b5d6645549..22f4554b33 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Trim.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Trim.php @@ -2,15 +2,10 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; - class Trim { - private static $invalidChars; - /** - * TRIMNONPRINTABLE. + * CLEAN. * * @param mixed $stringValue String Value to check * @@ -18,41 +13,22 @@ class Trim */ public static function nonPrintable($stringValue = '') { - $stringValue = Functions::flattenSingleValue($stringValue); - - if (is_bool($stringValue)) { - return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (self::$invalidChars === null) { - self::$invalidChars = range(chr(0), chr(31)); - } + $stringValue = Helpers::extractString($stringValue); - if (is_string($stringValue) || is_numeric($stringValue)) { - return str_replace(self::$invalidChars, '', trim($stringValue, "\x00..\x1F")); - } - - return null; + return preg_replace('/[\\x00-\\x1f]/', '', "$stringValue"); } /** - * TRIMSPACES. + * TRIM. * * @param mixed $stringValue String Value to check * - * @return null|string + * @return string */ public static function spaces($stringValue = '') { - $stringValue = Functions::flattenSingleValue($stringValue); - if (is_bool($stringValue)) { - return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (is_string($stringValue) || is_numeric($stringValue)) { - return trim(preg_replace('/ +/', ' ', trim($stringValue, ' ')), ' '); - } + $stringValue = Helpers::extractString($stringValue); - return null; + return trim(preg_replace('/ +/', ' ', trim("$stringValue", ' ')) ?? '', ' '); } } diff --git a/src/PhpSpreadsheet/Settings.php b/src/PhpSpreadsheet/Settings.php index 8fdccbad18..b51cd9d0b2 100644 --- a/src/PhpSpreadsheet/Settings.php +++ b/src/PhpSpreadsheet/Settings.php @@ -68,6 +68,11 @@ public static function setLocale($locale) return Calculation::getInstance()->setLocale($locale); } + public static function getLocale(): string + { + return Calculation::getInstance()->getLocale(); + } + /** * Identify to PhpSpreadsheet the external library to use for rendering charts. * diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 493fd07e41..99f3cfb09b 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -1234,6 +1234,7 @@ private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell return; } $objWriter->writeAttribute('t', 'str'); + $calculatedValue = StringHelper::controlCharacterPHP2OOXML($calculatedValue); } elseif (is_bool($calculatedValue)) { $objWriter->writeAttribute('t', 'b'); $calculatedValue = (int) $calculatedValue; diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/AllSetupTeardown.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/AllSetupTeardown.php new file mode 100644 index 0000000000..e0343b2b87 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/AllSetupTeardown.php @@ -0,0 +1,105 @@ +locale = Settings::getLocale(); + $this->compatibilityMode = Functions::getCompatibilityMode(); + } + + protected function tearDown(): void + { + Functions::setCompatibilityMode($this->compatibilityMode); + Settings::setLocale($this->locale); + $this->sheet = null; + if ($this->spreadsheet !== null) { + $this->spreadsheet->disconnectWorksheets(); + $this->spreadsheet = null; + } + } + + protected static function setOpenOffice(): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + } + + protected static function setGnumeric(): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_GNUMERIC); + } + + /** + * @param mixed $expectedResult + */ + protected function mightHaveException($expectedResult): void + { + if ($expectedResult === 'exception') { + $this->expectException(CalcException::class); + } + } + + /** + * @param mixed $value + */ + protected function setCell(string $cell, $value): void + { + if ($value !== null) { + if (is_string($value) && is_numeric($value)) { + $this->getSheet()->getCell($cell)->setValueExplicit($value, DataType::TYPE_STRING); + } else { + $this->getSheet()->getCell($cell)->setValue($value); + } + } + } + + protected function getSpreadsheet(): Spreadsheet + { + if ($this->spreadsheet !== null) { + return $this->spreadsheet; + } + $this->spreadsheet = new Spreadsheet(); + + return $this->spreadsheet; + } + + protected function getSheet(): Worksheet + { + if ($this->sheet !== null) { + return $this->sheet; + } + $this->sheet = $this->getSpreadsheet()->getActiveSheet(); + + return $this->sheet; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharNonPrintableTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharNonPrintableTest.php new file mode 100644 index 0000000000..3caadcb53e --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharNonPrintableTest.php @@ -0,0 +1,47 @@ +getActiveSheet(); + $sheet->getCell('B1')->setValue('=CHAR(2)'); + $sheet->getCell('C1')->setValue('=CHAR(127)'); + $hello = "hello\nthere"; + $sheet->getCell('D2')->setValue($hello); + $sheet->getCell('D1')->setValue('=D2'); + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $type); + $result = $reloadedSpreadsheet->getActiveSheet()->getCell('B1')->getCalculatedValue(); + self::assertEquals("\x02", $result); + $result = $reloadedSpreadsheet->getActiveSheet()->getCell('C1')->getCalculatedValue(); + self::assertEquals("\x7f", $result); + $result = $reloadedSpreadsheet->getActiveSheet()->getCell('D1')->getCalculatedValue(); + self::assertEquals($hello, $result); + $spreadsheet->disconnectWorksheets(); + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function providerType(): array + { + return [ + ['Xlsx'], + ['Xls'], + //['Ods'], // no support yet in Reader or Writer + // without csv suffix, Reader/Csv decides type via mime_get_type, + // and control character makes it guess application/octet-stream, + // so Reader/Csv decides it can't read it. + //['Csv'], + // DOMDocument.loadHTML() rejects '' even though legal html. + //['Html'], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharTest.php index e8934ce53c..66d01cb084 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CharTest.php @@ -2,10 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class CharTest extends TestCase +class CharTest extends AllSetupTeardown { /** * @dataProvider providerCHAR @@ -13,9 +10,17 @@ class CharTest extends TestCase * @param mixed $expectedResult * @param mixed $character */ - public function testCHAR($expectedResult, $character): void + public function testCHAR($expectedResult, $character = 'omitted'): void { - $result = TextData::CHARACTER($character); + $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); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CleanTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CleanTest.php index e3ad554493..03939882d1 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CleanTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CleanTest.php @@ -2,10 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class CleanTest extends TestCase +class CleanTest extends AllSetupTeardown { /** * @dataProvider providerCLEAN @@ -13,9 +10,17 @@ class CleanTest extends TestCase * @param mixed $expectedResult * @param mixed $value */ - public function testCLEAN($expectedResult, $value): void + public function testCLEAN($expectedResult, $value = 'omitted'): void { - $result = TextData::TRIMNONPRINTABLE($value); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($value === 'omitted') { + $sheet->getCell('B1')->setValue('=CLEAN()'); + } else { + $this->setCell('A1', $value); + $sheet->getCell('B1')->setValue('=CLEAN(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CodeTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CodeTest.php index 6f0f6b0654..52baf1a118 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CodeTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/CodeTest.php @@ -2,10 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class CodeTest extends TestCase +class CodeTest extends AllSetupTeardown { /** * @dataProvider providerCODE @@ -13,9 +10,17 @@ class CodeTest extends TestCase * @param mixed $expectedResult * @param mixed $character */ - public function testCODE($expectedResult, $character): void + public function testCODE($expectedResult, $character = 'omitted'): void { - $result = TextData::ASCIICODE($character); + $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); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php index 88ad6bfd71..6c9a871de7 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ConcatenateTest.php @@ -2,19 +2,27 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class ConcatenateTest extends TestCase +class ConcatenateTest extends AllSetupTeardown { /** * @dataProvider providerCONCATENATE * * @param mixed $expectedResult + * @param array $args */ public function testCONCATENATE($expectedResult, ...$args): void { - $result = TextData::CONCATENATE(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + $finalArg = ''; + $row = 0; + foreach ($args as $arg) { + ++$row; + $this->setCell("A$row", $arg); + $finalArg = "A1:A$row"; + } + $this->setCell('B1', "=CONCAT($finalArg)"); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/DeprecatedTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/DeprecatedTest.php new file mode 100644 index 0000000000..49ac421b84 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/DeprecatedTest.php @@ -0,0 +1,44 @@ +mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($amount === 'omitted') { + $sheet->getCell('B1')->setValue('=DOLLAR()'); + } elseif ($decimals === 'omitted') { + $this->setCell('A1', $amount); + $sheet->getCell('B1')->setValue('=DOLLAR(A1)'); + } else { + $this->setCell('A1', $amount); + $this->setCell('A2', $decimals); + $sheet->getCell('B1')->setValue('=DOLLAR(A1, A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ExactTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ExactTest.php index e856832f4c..fc0f481e16 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ExactTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ExactTest.php @@ -2,21 +2,31 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class ExactTest extends TestCase +class ExactTest extends AllSetupTeardown { /** * @dataProvider providerEXACT * * @param mixed $expectedResult - * @param array $args + * @param mixed $string1 + * @param mixed $string2 */ - public function testEXACT($expectedResult, ...$args): void + public function testEXACT($expectedResult, $string1 = 'omitted', $string2 = 'omitted'): void { - $result = TextData::EXACT(...$args); - self::assertSame($expectedResult, $result); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($string1 === 'omitted') { + $sheet->getCell('B1')->setValue('=EXACT()'); + } elseif ($string2 === 'omitted') { + $this->setCell('A1', $string1); + $sheet->getCell('B1')->setValue('=EXACT(A1)'); + } else { + $this->setCell('A1', $string1); + $this->setCell('A2', $string2); + $sheet->getCell('B1')->setValue('=EXACT(A1, A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); } public function providerEXACT(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/FindTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/FindTest.php index 3631c6a7b7..14e48b7a08 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/FindTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/FindTest.php @@ -2,19 +2,36 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class FindTest extends TestCase +class FindTest extends AllSetupTeardown { /** * @dataProvider providerFIND * * @param mixed $expectedResult + * @param mixed $string1 + * @param mixed $string2 + * @param mixed $start */ - public function testFIND($expectedResult, ...$args): void + public function testFIND($expectedResult, $string1 = 'omitted', $string2 = 'omitted', $start = 'omitted'): void { - $result = TextData::SEARCHSENSITIVE(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($string1 === 'omitted') { + $sheet->getCell('B1')->setValue('=FIND()'); + } elseif ($string2 === 'omitted') { + $this->setCell('A1', $string1); + $sheet->getCell('B1')->setValue('=FIND(A1)'); + } elseif ($start === 'omitted') { + $this->setCell('A1', $string1); + $this->setCell('A2', $string2); + $sheet->getCell('B1')->setValue('=FIND(A1, A2)'); + } else { + $this->setCell('A1', $string1); + $this->setCell('A2', $string2); + $this->setCell('A3', $start); + $sheet->getCell('B1')->setValue('=FIND(A1, A2, A3)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/FixedTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/FixedTest.php index ab3cfb7a7f..a6b6e19708 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/FixedTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/FixedTest.php @@ -2,19 +2,36 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class FixedTest extends TestCase +class FixedTest extends AllSetupTeardown { /** * @dataProvider providerFIXED * * @param mixed $expectedResult + * @param mixed $number + * @param mixed $decimals + * @param mixed $noCommas */ - public function testFIXED($expectedResult, ...$args): void + public function testFIXED($expectedResult, $number = 'omitted', $decimals = 'omitted', $noCommas = 'omitted'): void { - $result = TextData::FIXEDFORMAT(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=FIXED()'); + } elseif ($decimals === 'omitted') { + $this->setCell('A1', $number); + $sheet->getCell('B1')->setValue('=FIXED(A1)'); + } elseif ($noCommas === 'omitted') { + $this->setCell('A1', $number); + $this->setCell('A2', $decimals); + $sheet->getCell('B1')->setValue('=FIXED(A1, A2)'); + } else { + $this->setCell('A1', $number); + $this->setCell('A2', $decimals); + $this->setCell('A3', $noCommas); + $sheet->getCell('B1')->setValue('=FIXED(A1, A2, A3)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LeftTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LeftTest.php index c2508dfd62..37ea2f3dc1 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LeftTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LeftTest.php @@ -2,25 +2,33 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Settings; -use PHPUnit\Framework\TestCase; -class LeftTest extends TestCase +class LeftTest extends AllSetupTeardown { - protected function tearDown(): void - { - Settings::setLocale('en_US'); - } - /** * @dataProvider providerLEFT * * @param mixed $expectedResult + * @param mixed $str string from which to extract + * @param mixed $cnt number of characters to extract */ - public function testLEFT($expectedResult, ...$args): void + public function testLEFT($expectedResult, $str = 'omitted', $cnt = 'omitted'): void { - $result = TextData::LEFT(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($str === 'omitted') { + $sheet->getCell('B1')->setValue('=LEFT()'); + } elseif ($cnt === 'omitted') { + $this->setCell('A1', $str); + $sheet->getCell('B1')->setValue('=LEFT(A1)'); + } else { + $this->setCell('A1', $str); + $this->setCell('A2', $cnt); + $sheet->getCell('B1')->setValue('=LEFT(A1, A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } @@ -41,14 +49,15 @@ public function testLowerWithLocaleBoolean($expectedResult, $locale, $value, $ch { $newLocale = Settings::setLocale($locale); if ($newLocale === false) { - Settings::setLocale('en_US'); self::markTestSkipped('Unable to set locale for locale-specific test'); } - $result = TextData::LEFT($value, $characters); + $sheet = $this->getSheet(); + $this->setCell('A1', $value); + $this->setCell('A2', $characters); + $sheet->getCell('B1')->setValue('=LEFT(A1, A2)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); - - Settings::setLocale('en_US'); } public function providerLocaleLEFT(): array @@ -64,4 +73,111 @@ public function providerLocaleLEFT(): array ['ЛОЖ', 'bg', false, 3], ]; } + + /** + * @dataProvider providerCalculationTypeLEFTTrue + */ + public function testCalculationTypeTrue(string $type, string $resultB1, string $resultB2): void + { + Functions::setCompatibilityMode($type); + $sheet = $this->getSheet(); + $this->setCell('A1', true); + $this->setCell('A2', 'Hello'); + $this->setCell('B1', '=LEFT(A1, 1)'); + $this->setCell('B2', '=LEFT(A2, A1)'); + self::assertEquals($resultB1, $sheet->getCell('B1')->getCalculatedValue()); + self::assertEquals($resultB2, $sheet->getCell('B2')->getCalculatedValue()); + } + + public function providerCalculationTypeLEFTTrue(): array + { + return [ + 'Excel LEFT(true, 1) AND LEFT("hello", true)' => [ + Functions::COMPATIBILITY_EXCEL, + 'T', + 'H', + ], + 'Gnumeric LEFT(true, 1) AND LEFT("hello", true)' => [ + Functions::COMPATIBILITY_GNUMERIC, + 'T', + 'H', + ], + 'OpenOffice LEFT(true, 1) AND LEFT("hello", true)' => [ + Functions::COMPATIBILITY_OPENOFFICE, + '1', + '#VALUE!', + ], + ]; + } + + /** + * @dataProvider providerCalculationTypeLEFTFalse + */ + public function testCalculationTypeFalse(string $type, string $resultB1, string $resultB2): void + { + Functions::setCompatibilityMode($type); + $sheet = $this->getSheet(); + $this->setCell('A1', false); + $this->setCell('A2', 'Hello'); + $this->setCell('B1', '=LEFT(A1, 1)'); + $this->setCell('B2', '=LEFT(A2, A1)'); + self::assertEquals($resultB1, $sheet->getCell('B1')->getCalculatedValue()); + self::assertEquals($resultB2, $sheet->getCell('B2')->getCalculatedValue()); + } + + public function providerCalculationTypeLEFTFalse(): array + { + return [ + 'Excel LEFT(false, 1) AND LEFT("hello", false)' => [ + Functions::COMPATIBILITY_EXCEL, + 'F', + '', + ], + 'Gnumeric LEFT(false, 1) AND LEFT("hello", false)' => [ + Functions::COMPATIBILITY_GNUMERIC, + 'F', + '', + ], + 'OpenOffice LEFT(false, 1) AND LEFT("hello", false)' => [ + Functions::COMPATIBILITY_OPENOFFICE, + '0', + '#VALUE!', + ], + ]; + } + + /** + * @dataProvider providerCalculationTypeLEFTNull + */ + public function testCalculationTypeNull(string $type, string $resultB1, string $resultB2): void + { + Functions::setCompatibilityMode($type); + $sheet = $this->getSheet(); + $this->setCell('A2', 'Hello'); + $this->setCell('B1', '=LEFT(A1, 1)'); + $this->setCell('B2', '=LEFT(A2, A1)'); + self::assertEquals($resultB1, $sheet->getCell('B1')->getCalculatedValue()); + self::assertEquals($resultB2, $sheet->getCell('B2')->getCalculatedValue()); + } + + public function providerCalculationTypeLEFTNull(): array + { + return [ + 'Excel LEFT(null, 1) AND LEFT("hello", null)' => [ + Functions::COMPATIBILITY_EXCEL, + '', + '', + ], + 'Gnumeric LEFT(null, 1) AND LEFT("hello", null)' => [ + Functions::COMPATIBILITY_GNUMERIC, + '', + 'H', + ], + 'OpenOffice LEFT(null, 1) AND LEFT("hello", null)' => [ + Functions::COMPATIBILITY_OPENOFFICE, + '', + '', + ], + ]; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LenTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LenTest.php index 7ab36b677c..b2ce124e1a 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LenTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LenTest.php @@ -2,20 +2,25 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class LenTest extends TestCase +class LenTest extends AllSetupTeardown { /** * @dataProvider providerLEN * * @param mixed $expectedResult - * @param mixed $value + * @param mixed $str */ - public function testLEN($expectedResult, $value): void + public function testLEN($expectedResult, $str = 'omitted'): void { - $result = TextData::STRINGLENGTH($value); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($str === 'omitted') { + $sheet->getCell('B1')->setValue('=LEN()'); + } else { + $this->setCell('A1', $str); + $sheet->getCell('B1')->setValue('=LEN(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LowerTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LowerTest.php index 1d11f1e56a..4639624a2b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LowerTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/LowerTest.php @@ -2,26 +2,27 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; use PhpOffice\PhpSpreadsheet\Settings; -use PHPUnit\Framework\TestCase; -class LowerTest extends TestCase +class LowerTest extends AllSetupTeardown { - protected function tearDown(): void - { - Settings::setLocale('en_US'); - } - /** * @dataProvider providerLOWER * * @param mixed $expectedResult - * @param mixed $value + * @param mixed $str */ - public function testLOWER($expectedResult, $value): void + public function testLOWER($expectedResult, $str = 'omitted'): void { - $result = TextData::LOWERCASE($value); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($str === 'omitted') { + $sheet->getCell('B1')->setValue('=LOWER()'); + } else { + $this->setCell('A1', $str); + $sheet->getCell('B1')->setValue('=LOWER(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } @@ -41,14 +42,13 @@ public function testLowerWithLocaleBoolean($expectedResult, $locale, $value): vo { $newLocale = Settings::setLocale($locale); if ($newLocale === false) { - Settings::setLocale('en_US'); self::markTestSkipped('Unable to set locale for locale-specific test'); } - - $result = TextData::LOWERCASE($value); + $sheet = $this->getSheet(); + $this->setCell('A1', $value); + $sheet->getCell('B1')->setValue('=LOWER(A1)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); - - Settings::setLocale('en_US'); } public function providerLocaleLOWER(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/MidTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/MidTest.php index 267d689452..6bf058a524 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/MidTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/MidTest.php @@ -2,25 +2,39 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Settings; -use PHPUnit\Framework\TestCase; -class MidTest extends TestCase +class MidTest extends AllSetupTeardown { - protected function tearDown(): void - { - Settings::setLocale('en_US'); - } - /** * @dataProvider providerMID * * @param mixed $expectedResult + * @param mixed $str string from which to extract + * @param mixed $start position at which to start + * @param mixed $cnt number of characters to extract */ - public function testMID($expectedResult, ...$args): void + public function testMID($expectedResult, $str = 'omitted', $start = 'omitted', $cnt = 'omitted'): void { - $result = TextData::MID(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($str === 'omitted') { + $sheet->getCell('B1')->setValue('=MID()'); + } elseif ($start === 'omitted') { + $this->setCell('A1', $str); + $sheet->getCell('B1')->setValue('=MID(A1)'); + } elseif ($cnt === 'omitted') { + $this->setCell('A1', $str); + $this->setCell('A2', $start); + $sheet->getCell('B1')->setValue('=MID(A1, A2)'); + } else { + $this->setCell('A1', $str); + $this->setCell('A2', $start); + $this->setCell('A3', $cnt); + $sheet->getCell('B1')->setValue('=MID(A1, A2, A3)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } @@ -38,18 +52,20 @@ public function providerMID(): array * @param mixed $offset * @param mixed $characters */ - public function testLowerWithLocaleBoolean($expectedResult, $locale, $value, $offset, $characters): void + public function testMiddleWithLocaleBoolean($expectedResult, $locale, $value, $offset, $characters): void { $newLocale = Settings::setLocale($locale); if ($newLocale === false) { - Settings::setLocale('en_US'); self::markTestSkipped('Unable to set locale for locale-specific test'); } - $result = TextData::MID($value, $offset, $characters); + $sheet = $this->getSheet(); + $this->setCell('A1', $value); + $this->setCell('A2', $offset); + $this->setCell('A3', $characters); + $sheet->getCell('B1')->setValue('=MID(A1, A2, A3)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); - - Settings::setLocale('en_US'); } public function providerLocaleMID(): array @@ -65,4 +81,124 @@ public function providerLocaleMID(): array ['ОЖ', 'bg', false, 2, 2], ]; } + + /** + * @dataProvider providerCalculationTypeMIDTrue + */ + public function testCalculationTypeTrue(string $type, string $resultB1, string $resultB2, string $resultB3): void + { + Functions::setCompatibilityMode($type); + $sheet = $this->getSheet(); + $this->setCell('A1', true); + $this->setCell('A2', 'hello'); + $this->setCell('B1', '=MID(A1, 3, 1)'); + $this->setCell('B2', '=MID(A2, A1, 1)'); + $this->setCell('B3', '=MID(A2, 2, A1)'); + self::assertEquals($resultB1, $sheet->getCell('B1')->getCalculatedValue()); + self::assertEquals($resultB2, $sheet->getCell('B2')->getCalculatedValue()); + } + + public function providerCalculationTypeMIDTrue(): array + { + return [ + 'Excel MID(true,3,1), MID("hello",true, 1), MID("hello", 2, true)' => [ + Functions::COMPATIBILITY_EXCEL, + 'U', + 'h', + 'e', + ], + 'Gnumeric MID(true,3,1), MID("hello",true, 1), MID("hello", 2, true)' => [ + Functions::COMPATIBILITY_GNUMERIC, + 'U', + 'h', + 'e', + ], + 'OpenOffice MID(true,3,1), MID("hello",true, 1), MID("hello", 2, true)' => [ + Functions::COMPATIBILITY_OPENOFFICE, + '', + '#VALUE!', + '#VALUE!', + ], + ]; + } + + /** + * @dataProvider providerCalculationTypeMIDFalse + */ + public function testCalculationTypeFalse(string $type, string $resultB1, string $resultB2): void + { + Functions::setCompatibilityMode($type); + $sheet = $this->getSheet(); + $this->setCell('A1', false); + $this->setCell('A2', 'Hello'); + $this->setCell('B1', '=MID(A1, 3, 1)'); + $this->setCell('B2', '=MID(A2, A1, 1)'); + $this->setCell('B3', '=MID(A2, 2, A1)'); + self::assertEquals($resultB1, $sheet->getCell('B1')->getCalculatedValue()); + self::assertEquals($resultB2, $sheet->getCell('B2')->getCalculatedValue()); + } + + public function providerCalculationTypeMIDFalse(): array + { + return [ + 'Excel MID(false,3,1), MID("hello", false, 1), MID("hello", 2, false)' => [ + Functions::COMPATIBILITY_EXCEL, + 'L', + '#VALUE!', + '', + ], + 'Gnumeric MID(false,3,1), MID("hello", false, 1), MID("hello", 2, false)' => [ + Functions::COMPATIBILITY_GNUMERIC, + 'L', + '#VALUE!', + '', + ], + 'OpenOffice MID(false,3,1), MID("hello", false, 1), MID("hello", 2, false)' => [ + Functions::COMPATIBILITY_OPENOFFICE, + '', + '#VALUE!', + '#VALUE!', + ], + ]; + } + + /** + * @dataProvider providerCalculationTypeMIDNull + */ + public function testCalculationTypeNull(string $type, string $resultB1, string $resultB2, string $resultB3): void + { + Functions::setCompatibilityMode($type); + $sheet = $this->getSheet(); + $this->setCell('A2', 'Hello'); + $this->setCell('B1', '=MID(A1, 3, 1)'); + $this->setCell('B2', '=MID(A2, A1, 1)'); + $this->setCell('B3', '=MID(A2, 2, A1)'); + self::assertEquals($resultB1, $sheet->getCell('B1')->getCalculatedValue()); + self::assertEquals($resultB2, $sheet->getCell('B2')->getCalculatedValue()); + self::assertEquals($resultB3, $sheet->getCell('B3')->getCalculatedValue()); + } + + public function providerCalculationTypeMIDNull(): array + { + return [ + 'Excel MID(null,3,1), MID("hello", null, 1), MID("hello", 2, null)' => [ + Functions::COMPATIBILITY_EXCEL, + '', + '#VALUE!', + '', + ], + 'Gnumeric MID(null,3,1), MID("hello", null, 1), MID("hello", 2, null)' => [ + Functions::COMPATIBILITY_GNUMERIC, + '', + '#VALUE!', + '', + ], + 'OpenOffice MID(null,3,1), MID("hello", null, 1), MID("hello", 2, null)' => [ + Functions::COMPATIBILITY_OPENOFFICE, + '', + '#VALUE!', + '', + ], + ]; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/NumberValueTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/NumberValueTest.php index f54bfc3d9a..86d8ca8b9e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/NumberValueTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/NumberValueTest.php @@ -2,19 +2,36 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class NumberValueTest extends TestCase +class NumberValueTest extends AllSetupTeardown { /** * @dataProvider providerNUMBERVALUE * * @param mixed $expectedResult + * @param mixed $number + * @param mixed $decimal + * @param mixed $group */ - public function testNUMBERVALUE($expectedResult, array $args): void + public function testNUMBERVALUE($expectedResult, $number = 'omitted', $decimal = 'omitted', $group = 'omitted'): void { - $result = TextData::NUMBERVALUE(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($number === 'omitted') { + $sheet->getCell('B1')->setValue('=NUMBERVALUE()'); + } elseif ($decimal === 'omitted') { + $this->setCell('A1', $number); + $sheet->getCell('B1')->setValue('=NUMBERVALUE(A1)'); + } elseif ($group === 'omitted') { + $this->setCell('A1', $number); + $this->setCell('A2', $decimal); + $sheet->getCell('B1')->setValue('=NUMBERVALUE(A1, A2)'); + } else { + $this->setCell('A1', $number); + $this->setCell('A2', $decimal); + $this->setCell('A3', $group); + $sheet->getCell('B1')->setValue('=NUMBERVALUE(A1, A2, A3)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/OpenOfficeTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/OpenOfficeTest.php new file mode 100644 index 0000000000..7421e41aec --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/OpenOfficeTest.php @@ -0,0 +1,26 @@ +setOpenOffice(); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + $this->setCell('A1', $formula); + $result = $sheet->getCell('A1')->getCalculatedValue(); + self::assertEquals($expectedResult, $result); + } + + public function providerOpenOffice(): array + { + return require 'tests/data/Calculation/TextData/OpenOffice.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ProperTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ProperTest.php index 8366b07055..51968d83f7 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ProperTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ProperTest.php @@ -2,26 +2,27 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; use PhpOffice\PhpSpreadsheet\Settings; -use PHPUnit\Framework\TestCase; -class ProperTest extends TestCase +class ProperTest extends AllSetupTeardown { - protected function tearDown(): void - { - Settings::setLocale('en_US'); - } - /** * @dataProvider providerPROPER * * @param mixed $expectedResult - * @param mixed $value + * @param mixed $str */ - public function testPROPER($expectedResult, $value): void + public function testPROPER($expectedResult, $str = 'omitted'): void { - $result = TextData::PROPERCASE($value); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($str === 'omitted') { + $sheet->getCell('B1')->setValue('=PROPER()'); + } else { + $this->setCell('A1', $str); + $sheet->getCell('B1')->setValue('=PROPER(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } @@ -41,14 +42,13 @@ public function testLowerWithLocaleBoolean($expectedResult, $locale, $value): vo { $newLocale = Settings::setLocale($locale); if ($newLocale === false) { - Settings::setLocale('en_US'); self::markTestSkipped('Unable to set locale for locale-specific test'); } - - $result = TextData::PROPERCASE($value); + $sheet = $this->getSheet(); + $this->setCell('A1', $value); + $sheet->getCell('B1')->setValue('=PROPER(A1)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); - - Settings::setLocale('en_US'); } public function providerLocaleLOWER(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ReplaceTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ReplaceTest.php index 781e454cb2..0e12086429 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ReplaceTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ReplaceTest.php @@ -2,19 +2,43 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class ReplaceTest extends TestCase +class ReplaceTest extends AllSetupTeardown { /** * @dataProvider providerREPLACE * * @param mixed $expectedResult + * @param mixed $oldText + * @param mixed $start + * @param mixed $count + * @param mixed $newText */ - public function testREPLACE($expectedResult, ...$args): void + public function testREPLACE($expectedResult, $oldText = 'omitted', $start = 'omitted', $count = 'omitted', $newText = 'omitted'): void { - $result = TextData::REPLACE(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($oldText === 'omitted') { + $sheet->getCell('B1')->setValue('=REPLACE()'); + } elseif ($start === 'omitted') { + $this->setCell('A1', $oldText); + $sheet->getCell('B1')->setValue('=REPLACE(A1)'); + } elseif ($count === 'omitted') { + $this->setCell('A1', $oldText); + $this->setCell('A2', $start); + $sheet->getCell('B1')->setValue('=REPLACE(A1, A2)'); + } elseif ($newText === 'omitted') { + $this->setCell('A1', $oldText); + $this->setCell('A2', $start); + $this->setCell('A3', $count); + $sheet->getCell('B1')->setValue('=REPLACE(A1, A2, A3)'); + } else { + $this->setCell('A1', $oldText); + $this->setCell('A2', $start); + $this->setCell('A3', $count); + $this->setCell('A4', $newText); + $sheet->getCell('B1')->setValue('=REPLACE(A1, A2, A3, A4)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ReptTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ReptTest.php index a0c089cf66..ea86d1b60e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ReptTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ReptTest.php @@ -2,13 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PHPUnit\Framework\TestCase; - -class ReptTest extends TestCase +class ReptTest extends AllSetupTeardown { /** * @dataProvider providerREPT @@ -17,37 +11,21 @@ class ReptTest extends TestCase * @param mixed $val * @param mixed $rpt */ - public function testReptDirect($expectedResult, $val = null, $rpt = null): void - { - $result = TextData::builtinREPT(is_string($val) ? trim($val, '"') : $val, $rpt); - self::assertEquals($expectedResult, $result); - } - - /** - * @dataProvider providerREPT - * - * @param mixed $expectedResult - * @param mixed $val - * @param mixed $rpt - */ - public function testReptThroughEngine($expectedResult, $val = null, $rpt = null): void + public function testReptThroughEngine($expectedResult, $val = 'omitted', $rpt = 'omitted'): void { - if ($val === null) { - $this->expectException(CalcExp::class); - $formula = '=REPT()'; - } elseif ($rpt === null) { - $this->expectException(CalcExp::class); - $formula = "=REPT($val)"; + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($val === 'omitted') { + $sheet->getCell('B1')->setValue('=REPT()'); + } elseif ($rpt === 'omitted') { + $this->setCell('A1', $val); + $sheet->getCell('B1')->setValue('=REPT(A1)'); } else { - if (is_bool($val)) { - $val = ($val) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - $formula = "=REPT($val, $rpt)"; + $this->setCell('A1', $val); + $this->setCell('A2', $rpt); + $sheet->getCell('B1')->setValue('=REPT(A1, A2)'); } - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); - $sheet->getCell('A1')->setValue($formula); - $result = $sheet->getCell('A1')->getCalculatedValue(); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/RightTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/RightTest.php index a2ab3a4c36..b9051d0da1 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/RightTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/RightTest.php @@ -2,25 +2,33 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Settings; -use PHPUnit\Framework\TestCase; -class RightTest extends TestCase +class RightTest extends AllSetupTeardown { - protected function tearDown(): void - { - Settings::setLocale('en_US'); - } - /** * @dataProvider providerRIGHT * * @param mixed $expectedResult + * @param mixed $str string from which to extract + * @param mixed $cnt number of characters to extract */ - public function testRIGHT($expectedResult, ...$args): void + public function testRIGHT($expectedResult, $str = 'omitted', $cnt = 'omitted'): void { - $result = TextData::RIGHT(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($str === 'omitted') { + $sheet->getCell('B1')->setValue('=RIGHT()'); + } elseif ($cnt === 'omitted') { + $this->setCell('A1', $str); + $sheet->getCell('B1')->setValue('=RIGHT(A1)'); + } else { + $this->setCell('A1', $str); + $this->setCell('A2', $cnt); + $sheet->getCell('B1')->setValue('=RIGHT(A1, A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } @@ -41,14 +49,15 @@ public function testLowerWithLocaleBoolean($expectedResult, $locale, $value, $ch { $newLocale = Settings::setLocale($locale); if ($newLocale === false) { - Settings::setLocale('en_US'); self::markTestSkipped('Unable to set locale for locale-specific test'); } - $result = TextData::RIGHT($value, $characters); + $sheet = $this->getSheet(); + $this->setCell('A1', $value); + $this->setCell('A2', $characters); + $sheet->getCell('B1')->setValue('=RIGHT(A1, A2)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); - - Settings::setLocale('en_US'); } public function providerLocaleRIGHT(): array @@ -64,4 +73,111 @@ public function providerLocaleRIGHT(): array ['ЖЬ', 'bg', false, 2], ]; } + + /** + * @dataProvider providerCalculationTypeRIGHTTrue + */ + public function testCalculationTypeTrue(string $type, string $resultB1, string $resultB2): void + { + Functions::setCompatibilityMode($type); + $sheet = $this->getSheet(); + $this->setCell('A1', true); + $this->setCell('A2', 'Hello'); + $this->setCell('B1', '=RIGHT(A1, 1)'); + $this->setCell('B2', '=RIGHT(A2, A1)'); + self::assertEquals($resultB1, $sheet->getCell('B1')->getCalculatedValue()); + self::assertEquals($resultB2, $sheet->getCell('B2')->getCalculatedValue()); + } + + public function providerCalculationTypeRIGHTTrue(): array + { + return [ + 'Excel RIGHT(true, 1) AND RIGHT("hello", true)' => [ + Functions::COMPATIBILITY_EXCEL, + 'E', + 'o', + ], + 'Gnumeric RIGHT(true, 1) AND RIGHT("hello", true)' => [ + Functions::COMPATIBILITY_GNUMERIC, + 'E', + 'o', + ], + 'OpenOffice RIGHT(true, 1) AND RIGHT("hello", true)' => [ + Functions::COMPATIBILITY_OPENOFFICE, + '1', + '#VALUE!', + ], + ]; + } + + /** + * @dataProvider providerCalculationTypeRIGHTFalse + */ + public function testCalculationTypeFalse(string $type, string $resultB1, string $resultB2): void + { + Functions::setCompatibilityMode($type); + $sheet = $this->getSheet(); + $this->setCell('A1', false); + $this->setCell('A2', 'Hello'); + $this->setCell('B1', '=RIGHT(A1, 1)'); + $this->setCell('B2', '=RIGHT(A2, A1)'); + self::assertEquals($resultB1, $sheet->getCell('B1')->getCalculatedValue()); + self::assertEquals($resultB2, $sheet->getCell('B2')->getCalculatedValue()); + } + + public function providerCalculationTypeRIGHTFalse(): array + { + return [ + 'Excel RIGHT(false, 1) AND RIGHT("hello", false)' => [ + Functions::COMPATIBILITY_EXCEL, + 'E', + '', + ], + 'Gnumeric RIGHT(false, 1) AND RIGHT("hello", false)' => [ + Functions::COMPATIBILITY_GNUMERIC, + 'E', + '', + ], + 'OpenOffice RIGHT(false, 1) AND RIGHT("hello", false)' => [ + Functions::COMPATIBILITY_OPENOFFICE, + '0', + '#VALUE!', + ], + ]; + } + + /** + * @dataProvider providerCalculationTypeRIGHTNull + */ + public function testCalculationTypeNull(string $type, string $resultB1, string $resultB2): void + { + Functions::setCompatibilityMode($type); + $sheet = $this->getSheet(); + $this->setCell('A2', 'Hello'); + $this->setCell('B1', '=RIGHT(A1, 1)'); + $this->setCell('B2', '=RIGHT(A2, A1)'); + self::assertEquals($resultB1, $sheet->getCell('B1')->getCalculatedValue()); + self::assertEquals($resultB2, $sheet->getCell('B2')->getCalculatedValue()); + } + + public function providerCalculationTypeRIGHTNull(): array + { + return [ + 'Excel RIGHT(null, 1) AND RIGHT("hello", null)' => [ + Functions::COMPATIBILITY_EXCEL, + '', + '', + ], + 'Gnumeric RIGHT(null, 1) AND RIGHT("hello", null)' => [ + Functions::COMPATIBILITY_GNUMERIC, + '', + 'o', + ], + 'OpenOffice RIGHT(null, 1) AND RIGHT("hello", null)' => [ + Functions::COMPATIBILITY_OPENOFFICE, + '', + '', + ], + ]; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SearchTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SearchTest.php index 14f5735ba7..5f1c526d1d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SearchTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SearchTest.php @@ -2,19 +2,36 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class SearchTest extends TestCase +class SearchTest extends AllSetupTeardown { /** * @dataProvider providerSEARCH * * @param mixed $expectedResult + * @param mixed $findText + * @param mixed $withinText + * @param mixed $start */ - public function testSEARCH($expectedResult, ...$args): void + public function testSEARCH($expectedResult, $findText = 'omitted', $withinText = 'omitted', $start = 'omitted'): void { - $result = TextData::SEARCHINSENSITIVE(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($findText === 'omitted') { + $sheet->getCell('B1')->setValue('=SEARCH()'); + } elseif ($withinText === 'omitted') { + $this->setCell('A1', $findText); + $sheet->getCell('B1')->setValue('=SEARCH(A1)'); + } elseif ($start === 'omitted') { + $this->setCell('A1', $findText); + $this->setCell('A2', $withinText); + $sheet->getCell('B1')->setValue('=SEARCH(A1, A2)'); + } else { + $this->setCell('A1', $findText); + $this->setCell('A2', $withinText); + $this->setCell('A3', $start); + $sheet->getCell('B1')->setValue('=SEARCH(A1, A2, A3)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SubstituteTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SubstituteTest.php index 792e1a1520..bbb055912c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SubstituteTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/SubstituteTest.php @@ -2,19 +2,43 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class SubstituteTest extends TestCase +class SubstituteTest extends AllSetupTeardown { /** * @dataProvider providerSUBSTITUTE * * @param mixed $expectedResult + * @param mixed $text + * @param mixed $oldText + * @param mixed $newText + * @param mixed $instance */ - public function testSUBSTITUTE($expectedResult, ...$args): void + public function testSUBSTITUTE($expectedResult, $text = 'omitted', $oldText = 'omitted', $newText = 'omitted', $instance = 'omitted'): void { - $result = TextData::SUBSTITUTE(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($text === 'omitted') { + $sheet->getCell('B1')->setValue('=SUBSTITUTE()'); + } elseif ($oldText === 'omitted') { + $this->setCell('A1', $text); + $sheet->getCell('B1')->setValue('=SUBSTITUTE(A1)'); + } elseif ($newText === 'omitted') { + $this->setCell('A1', $text); + $this->setCell('A2', $oldText); + $sheet->getCell('B1')->setValue('=SUBSTITUTE(A1, A2)'); + } elseif ($instance === 'omitted') { + $this->setCell('A1', $text); + $this->setCell('A2', $oldText); + $this->setCell('A3', $newText); + $sheet->getCell('B1')->setValue('=SUBSTITUTE(A1, A2, A3)'); + } else { + $this->setCell('A1', $text); + $this->setCell('A2', $oldText); + $this->setCell('A3', $newText); + $this->setCell('A4', $instance); + $sheet->getCell('B1')->setValue('=SUBSTITUTE(A1, A2, A3, A4)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextJoinTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextJoinTest.php index 78e72f967e..60b21661cb 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextJoinTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextJoinTest.php @@ -2,10 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class TextJoinTest extends TestCase +class TextJoinTest extends AllSetupTeardown { /** * @dataProvider providerTEXTJOIN @@ -14,7 +11,20 @@ class TextJoinTest extends TestCase */ public function testTEXTJOIN($expectedResult, array $args): void { - $result = TextData::TEXTJOIN(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + $b1Formula = '=TEXTJOIN('; + $comma = ''; + $row = 0; + foreach ($args as $arg) { + ++$row; + $this->setCell("A$row", $arg); + $b1Formula .= $comma . "A$row"; + $comma = ','; + } + $b1Formula .= ')'; + $this->setCell('B1', $b1Formula); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextTest.php index 914180652f..3ede2ffe9e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TextTest.php @@ -2,19 +2,30 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class TextTest extends TestCase +class TextTest extends AllSetupTeardown { /** * @dataProvider providerTEXT * * @param mixed $expectedResult + * @param mixed $value + * @param mixed $format */ - public function testTEXT($expectedResult, ...$args): void + public function testTEXT($expectedResult, $value = 'omitted', $format = 'omitted'): void { - $result = TextData::TEXTFORMAT(...$args); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($value === 'omitted') { + $sheet->getCell('B1')->setValue('=TEXT()'); + } elseif ($format === 'omitted') { + $this->setCell('A1', $value); + $sheet->getCell('B1')->setValue('=TEXT(A1)'); + } else { + $this->setCell('A1', $value); + $this->setCell('A2', $format); + $sheet->getCell('B1')->setValue('=TEXT(A1, A2)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TrimTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TrimTest.php index 302dce4f9b..133e850f64 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TrimTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/TrimTest.php @@ -2,10 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; -use PHPUnit\Framework\TestCase; - -class TrimTest extends TestCase +class TrimTest extends AllSetupTeardown { /** * @dataProvider providerTRIM @@ -13,9 +10,17 @@ class TrimTest extends TestCase * @param mixed $expectedResult * @param mixed $character */ - public function testTRIM($expectedResult, $character): void + public function testTRIM($expectedResult, $character = 'omitted'): void { - $result = TextData::TRIMSPACES($character); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($character === 'omitted') { + $sheet->getCell('B1')->setValue('=TRIM()'); + } else { + $this->setCell('A1', $character); + $sheet->getCell('B1')->setValue('=TRIM(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UpperTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UpperTest.php index 45f7aea888..0147b5c183 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UpperTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/UpperTest.php @@ -2,26 +2,27 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; use PhpOffice\PhpSpreadsheet\Settings; -use PHPUnit\Framework\TestCase; -class UpperTest extends TestCase +class UpperTest extends AllSetupTeardown { - protected function tearDown(): void - { - Settings::setLocale('en_US'); - } - /** * @dataProvider providerUPPER * * @param mixed $expectedResult - * @param mixed $value + * @param mixed $str */ - public function testUPPER($expectedResult, $value): void + public function testUPPER($expectedResult, $str = 'omitted'): void { - $result = TextData::UPPERCASE($value); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($str === 'omitted') { + $sheet->getCell('B1')->setValue('=UPPER()'); + } else { + $this->setCell('A1', $str); + $sheet->getCell('B1')->setValue('=UPPER(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); } @@ -41,14 +42,13 @@ public function testLowerWithLocaleBoolean($expectedResult, $locale, $value): vo { $newLocale = Settings::setLocale($locale); if ($newLocale === false) { - Settings::setLocale('en_US'); self::markTestSkipped('Unable to set locale for locale-specific test'); } - - $result = TextData::UPPERCASE($value); + $sheet = $this->getSheet(); + $this->setCell('A1', $value); + $sheet->getCell('B1')->setValue('=UPPER(A1)'); + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEquals($expectedResult, $result); - - Settings::setLocale('en_US'); } public function providerLocaleLOWER(): array diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ValueTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ValueTest.php index b3ba1246cb..fbef254b4b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ValueTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ValueTest.php @@ -2,11 +2,9 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\TextData; -use PhpOffice\PhpSpreadsheet\Calculation\TextData; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; -use PHPUnit\Framework\TestCase; -class ValueTest extends TestCase +class ValueTest extends AllSetupTeardown { /** * @var string @@ -25,6 +23,7 @@ class ValueTest extends TestCase protected function setUp(): void { + parent::setUp(); $this->currencyCode = StringHelper::getCurrencyCode(); $this->decimalSeparator = StringHelper::getDecimalSeparator(); $this->thousandsSeparator = StringHelper::getThousandsSeparator(); @@ -32,6 +31,7 @@ protected function setUp(): void protected function tearDown(): void { + parent::tearDown(); StringHelper::setCurrencyCode($this->currencyCode); StringHelper::setDecimalSeparator($this->decimalSeparator); StringHelper::setThousandsSeparator($this->thousandsSeparator); @@ -43,13 +43,21 @@ protected function tearDown(): void * @param mixed $expectedResult * @param mixed $value */ - public function testVALUE($expectedResult, $value): void + public function testVALUE($expectedResult, $value = 'omitted'): void { StringHelper::setDecimalSeparator('.'); StringHelper::setThousandsSeparator(' '); StringHelper::setCurrencyCode('$'); - $result = TextData::VALUE($value); + $this->mightHaveException($expectedResult); + $sheet = $this->getSheet(); + if ($value === 'omitted') { + $sheet->getCell('B1')->setValue('=VALUE()'); + } else { + $this->setCell('A1', $value); + $sheet->getCell('B1')->setValue('=VALUE(A1)'); + } + $result = $sheet->getCell('B1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } diff --git a/tests/data/Calculation/TextData/CHAR.php b/tests/data/Calculation/TextData/CHAR.php index 276d701518..f02437b7d0 100644 --- a/tests/data/Calculation/TextData/CHAR.php +++ b/tests/data/Calculation/TextData/CHAR.php @@ -61,4 +61,10 @@ '#VALUE!', // '†', 0x2020, ], + 'omitted argument' => ['exception'], + 'non-printable' => ["\x02", 2], + 'bool argument' => ["\x01", true], + 'null argument' => ['#VALUE!', null], + 'ascii 1 is 49' => ['1', 49], + 'ascii 0 is 48' => ['0', 48], ]; diff --git a/tests/data/Calculation/TextData/CLEAN.php b/tests/data/Calculation/TextData/CLEAN.php index 6760888327..ad668271a3 100644 --- a/tests/data/Calculation/TextData/CLEAN.php +++ b/tests/data/Calculation/TextData/CLEAN.php @@ -25,4 +25,5 @@ null, null, ], + 'omitted argument' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/CODE.php b/tests/data/Calculation/TextData/CODE.php index 1b74b69918..8ab927263e 100644 --- a/tests/data/Calculation/TextData/CODE.php +++ b/tests/data/Calculation/TextData/CODE.php @@ -69,4 +69,5 @@ 0x2020, '†', ], + 'omitted argument' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/CONCATENATE.php b/tests/data/Calculation/TextData/CONCATENATE.php index f17b024bb3..c6b583eb2b 100644 --- a/tests/data/Calculation/TextData/CONCATENATE.php +++ b/tests/data/Calculation/TextData/CONCATENATE.php @@ -18,4 +18,5 @@ '-', true, ], + 'no arguments' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/DOLLAR.php b/tests/data/Calculation/TextData/DOLLAR.php index 205527b8d2..78fcc1b52e 100644 --- a/tests/data/Calculation/TextData/DOLLAR.php +++ b/tests/data/Calculation/TextData/DOLLAR.php @@ -45,4 +45,10 @@ 123.456, 'ABC', ], + 'omitted amount' => ['exception'], + 'omitted decimals' => ['$123.46', 123.456], + 'null decimals' => ['$123', 123.456, null], + 'boolean decimals' => ['$123.5', 123.456, true], + 'boolean value' => ['$1.00', true], + 'null value' => ['$0.00', null], ]; diff --git a/tests/data/Calculation/TextData/EXACT.php b/tests/data/Calculation/TextData/EXACT.php index f760a58fbc..f392b0b055 100644 --- a/tests/data/Calculation/TextData/EXACT.php +++ b/tests/data/Calculation/TextData/EXACT.php @@ -36,4 +36,6 @@ ' ', '', ], + 'no arguments' => ['exception'], + 'one argument1' => ['exception', 'abc'], ]; diff --git a/tests/data/Calculation/TextData/FIND.php b/tests/data/Calculation/TextData/FIND.php index 04d3276d47..1ae6d730ea 100644 --- a/tests/data/Calculation/TextData/FIND.php +++ b/tests/data/Calculation/TextData/FIND.php @@ -106,4 +106,6 @@ true, 'Mark Baker', ], + 'no arguments' => ['exception'], + 'one argument' => ['exception', 'a'], ]; diff --git a/tests/data/Calculation/TextData/FIXED.php b/tests/data/Calculation/TextData/FIXED.php index 2083eccebe..29734404cd 100644 --- a/tests/data/Calculation/TextData/FIXED.php +++ b/tests/data/Calculation/TextData/FIXED.php @@ -59,4 +59,9 @@ 'ABC', null, ], + 'no arguments' => ['exception'], + 'just one argument is okay' => ['123.00', 123], + 'null second argument' => ['123', 123, null], + 'false second argument' => ['123', 123, false], + 'true second argument' => ['123.0', 123, true], ]; diff --git a/tests/data/Calculation/TextData/LEFT.php b/tests/data/Calculation/TextData/LEFT.php index d524dc36f8..17d3fcf7a5 100644 --- a/tests/data/Calculation/TextData/LEFT.php +++ b/tests/data/Calculation/TextData/LEFT.php @@ -26,11 +26,15 @@ 'QWERTYUIOP', 'NaN', ], - [ - '#VALUE!', + 'null length defaults to 0' => [ + '', 'QWERTYUIOP', null, ], + 'omitted length defaults to 1' => [ + 'Q', + 'QWERTYUIOP', + ], [ 'ABC', 'ABCDEFGHI', @@ -61,4 +65,7 @@ false, 2, ], + 'string not specified' => [ + 'exception', + ], ]; diff --git a/tests/data/Calculation/TextData/LEN.php b/tests/data/Calculation/TextData/LEN.php index 6bf44fc9e5..babf3535a6 100644 --- a/tests/data/Calculation/TextData/LEN.php +++ b/tests/data/Calculation/TextData/LEN.php @@ -25,4 +25,5 @@ 5, false, ], + 'no arguments' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/LOWER.php b/tests/data/Calculation/TextData/LOWER.php index c5360b959d..23825546d2 100644 --- a/tests/data/Calculation/TextData/LOWER.php +++ b/tests/data/Calculation/TextData/LOWER.php @@ -29,4 +29,5 @@ 'false', false, ], + 'no argument' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/MID.php b/tests/data/Calculation/TextData/MID.php index b434f6704d..45e0ceab9b 100644 --- a/tests/data/Calculation/TextData/MID.php +++ b/tests/data/Calculation/TextData/MID.php @@ -37,11 +37,24 @@ 2, 'NaN', ], - [ - '#VALUE!', + 'length null treated as zero' => [ + '', + 'QWERTYUIOP', + 2, + null, + ], + 'length not specified' => [ + 'exception', 'QWERTYUIOP', 5, ], + 'start not specified' => [ + 'exception', + 'QWERTYUIOP', + ], + 'string not specified' => [ + 'exception', + ], [ 'IOP', 'QWERTYUIOP', diff --git a/tests/data/Calculation/TextData/NUMBERVALUE.php b/tests/data/Calculation/TextData/NUMBERVALUE.php index 3a6510b848..791e61cf85 100644 --- a/tests/data/Calculation/TextData/NUMBERVALUE.php +++ b/tests/data/Calculation/TextData/NUMBERVALUE.php @@ -3,50 +3,52 @@ return [ [ 1234567.89, - ['1,234,567.890'], + '1,234,567.890', ], [ 1234567.89, - ['1 234 567,890', ',', ' '], + '1 234 567,890', ',', ' ', ], [ -1234567.89, - ['-1 234 567,890', ',', ' '], + '-1 234 567,890', ',', ' ', ], [ '#VALUE!', - ['1 234 567,890-', ',', ' '], + '1 234 567,890-', ',', ' ', ], [ '#VALUE!', - ['1,234,567.890,123'], + '1,234,567.890,123', ], [ '#VALUE!', - ['1.234.567.890,123'], + '1.234.567.890,123', ], [ 1234567.890, - ['1.234.567,890', ',', '.'], + '1.234.567,890', ',', '.', ], [ '#VALUE!', - ['1.234.567,89'], + '1.234.567,89', ], [ 12345.6789, - ['1,234,567.89%'], + '1,234,567.89%', ], [ 123.456789, - ['1,234,567.89%%'], + '1,234,567.89%%', ], [ 1.23456789, - ['1,234,567.89%%%'], + '1,234,567.89%%%', ], [ '#VALUE!', - ['1,234,567.89-%'], + '1,234,567.89-%', ], + 'no arguments' => ['exception'], + 'boolean argument' => ['#VALUE!', true], ]; diff --git a/tests/data/Calculation/TextData/OpenOffice.php b/tests/data/Calculation/TextData/OpenOffice.php new file mode 100644 index 0000000000..fc79d5f11c --- /dev/null +++ b/tests/data/Calculation/TextData/OpenOffice.php @@ -0,0 +1,28 @@ + ["\x00", '=CHAR(0)'], + 'OO treats CODE(bool) as 0/1' => ['48', '=CODE(FALSE)'], + '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 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")'], + 'OO treats bool as string as 0/1 to FIND parm 2' => [1, '=FIND(0, false)'], + 'OO treats true as int 1 to FIND parm 3' => [1, '=FIND("a", "aba", true)'], + 'OO treats false as int 0 to FIND parm 3' => ['#VALUE!', '=FIND("a", "aba", false)'], + 'OO treats bool as string as 0/1 to SEARCH parm 1' => [2, '=SEARCH(true, "210")'], + 'OO treats bool as string as 0/1 to SEARCH parm 2' => [1, '=SEARCH(0, false)'], + 'OO treats true as int 1 to SEARCH parm 3' => [1, '=SEARCH("a", "aba", true)'], + 'OO treats false as int 0 to SEARCH parm 3' => ['#VALUE!', '=SEARCH("a", "aba", false)'], + 'OO treats true as 1 to REPLACE parm 1' => ['10', '=REPLACE(true, 3, 1, false)'], + 'OO treats false as 0 to REPLACE parm 4' => ['he0lo', '=REPLACE("hello", 3, 1, false)'], + '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 TEXTJOIN boolean in lieu of string' => ['1-0-1', '=TEXTJOIN("-", true, true, false, true)'], +]; diff --git a/tests/data/Calculation/TextData/PROPER.php b/tests/data/Calculation/TextData/PROPER.php index 8bbf0e5c66..e9a5d4ceb6 100644 --- a/tests/data/Calculation/TextData/PROPER.php +++ b/tests/data/Calculation/TextData/PROPER.php @@ -25,4 +25,5 @@ 'False', false, ], + 'no argument' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/REPLACE.php b/tests/data/Calculation/TextData/REPLACE.php index 09e2296825..8f02e2e36d 100644 --- a/tests/data/Calculation/TextData/REPLACE.php +++ b/tests/data/Calculation/TextData/REPLACE.php @@ -57,4 +57,12 @@ 0, ' ', ], + 'no arguments' => ['exception'], + 'one argument' => ['exception', 'hello'], + 'two arguments' => ['exception', 'hello', 2], + 'three arguments' => ['exception', 'hello', 2, 2], + 'position zero' => ['#VALUE!', 'hello', 0, 2, 'xyz'], + 'negative length' => ['#VALUE!', 'hello', 3, -1, 'xyz'], + 'boolean 1st parm' => ['TRDFGE', true, 3, 1, 'DFG'], + 'boolean 4th parm' => ['heFALSElo', 'hello', 3, 1, false], ]; diff --git a/tests/data/Calculation/TextData/REPT.php b/tests/data/Calculation/TextData/REPT.php index 2c8d1c0d53..f8aef72c8b 100644 --- a/tests/data/Calculation/TextData/REPT.php +++ b/tests/data/Calculation/TextData/REPT.php @@ -1,14 +1,14 @@ ['exception'], + 'one argument' => ['exception', 'ABC'], + ['#VALUE!', 'ABC', 'DEF'], + ['ABCABCABC', 'ABC', 3], + ['ABCABC', 'ABC', 2.2], + ['', 'ABC', 0], ['TRUETRUE', true, 2], ['111', 1, 3], - ['δύο δύο ', '"δύο "', 2], - ['#VALUE!', '"ABC"', -1], + ['δύο δύο ', 'δύο ', 2], + ['#VALUE!', 'ABC', -1], ]; diff --git a/tests/data/Calculation/TextData/RIGHT.php b/tests/data/Calculation/TextData/RIGHT.php index e6928df229..77ea64bf71 100644 --- a/tests/data/Calculation/TextData/RIGHT.php +++ b/tests/data/Calculation/TextData/RIGHT.php @@ -21,11 +21,15 @@ 'QWERTYUIOP', 'NaN', ], - [ - '#VALUE!', + 'null length defaults to 0' => [ + '', 'QWERTYUIOP', null, ], + 'omitted length defaults to 1' => [ + 'P', + 'QWERTYUIOP', + ], [ 'GHI', 'ABCDEFGHI', @@ -61,4 +65,7 @@ false, 2, ], + 'string not specified' => [ + 'exception', + ], ]; diff --git a/tests/data/Calculation/TextData/SEARCH.php b/tests/data/Calculation/TextData/SEARCH.php index fa970bec43..335a7f12a8 100644 --- a/tests/data/Calculation/TextData/SEARCH.php +++ b/tests/data/Calculation/TextData/SEARCH.php @@ -99,4 +99,6 @@ true, 'Mark Baker', ], + 'no arguments' => ['exception'], + 'one argument' => ['exception', 'string'], ]; diff --git a/tests/data/Calculation/TextData/SUBSTITUTE.php b/tests/data/Calculation/TextData/SUBSTITUTE.php index 97cb8d0ff5..9b695a715e 100644 --- a/tests/data/Calculation/TextData/SUBSTITUTE.php +++ b/tests/data/Calculation/TextData/SUBSTITUTE.php @@ -73,4 +73,16 @@ "\u{00E5}", 'x', ], + 'no arguments' => ['exception'], + 'one argument' => ['exception', 'a'], + 'two arguments' => ['exception', 'a', 'b'], + 'negative instance' => ['#VALUE!', 'abcdefg', 'def', 123, -1], + 'non-numeric instance' => ['#VALUE!', 'abcdefg', 'def', 123, 'xyz'], + 'null instance' => ['abc123g', 'abcdefg', 'def', 123], + '0 instance' => ['#VALUE!', 'abcdefg', 'def', 123, 0], + '1 instance' => ['abc123g', 'abcdefg', 'def', 123, 1], + 'past last instance' => ['abcdefg', 'abcdefg', 'def', 123, 2], + 'bool false instance' => ['#VALUE!', 'abcdefg', 'def', '123', false], + 'bool true instance' => ['#VALUE!', 'abcdefg', 'def', '123', true], + 'bool text' => ['FA-SE', false, 'L', '-'], ]; diff --git a/tests/data/Calculation/TextData/TEXT.php b/tests/data/Calculation/TextData/TEXT.php index fcb05191ae..1c7c338adb 100644 --- a/tests/data/Calculation/TextData/TEXT.php +++ b/tests/data/Calculation/TextData/TEXT.php @@ -66,4 +66,7 @@ 1.75, '# ?/?', ], + 'no arguments' => ['exception'], + 'one argument' => ['exception', 1.75], + 'boolean in lieu of string' => ['TRUE', true, '@'], ]; diff --git a/tests/data/Calculation/TextData/TEXTJOIN.php b/tests/data/Calculation/TextData/TEXTJOIN.php index 9c6b424689..e345f7c177 100644 --- a/tests/data/Calculation/TextData/TEXTJOIN.php +++ b/tests/data/Calculation/TextData/TEXTJOIN.php @@ -37,4 +37,9 @@ 'C:\\Users\\Mark\\Documents\\notes.doc', ['\\', true, 'C:', 'Users', 'Mark', 'Documents', 'notes.doc'], ], + 'no argument' => ['exception', []], + 'one argument' => ['exception', ['-']], + 'two arguments' => ['exception', ['-', true]], + 'three arguments' => ['a', ['-', true, 'a']], + 'boolean as string' => ['TRUE-FALSE-TRUE', ['-', true, true, false, true]], ]; diff --git a/tests/data/Calculation/TextData/TRIM.php b/tests/data/Calculation/TextData/TRIM.php index f400f1872f..882dc824d1 100644 --- a/tests/data/Calculation/TextData/TRIM.php +++ b/tests/data/Calculation/TextData/TRIM.php @@ -29,4 +29,5 @@ null, null, ], + 'no arguments' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/UPPER.php b/tests/data/Calculation/TextData/UPPER.php index e5d2f18eae..21b04ae46a 100644 --- a/tests/data/Calculation/TextData/UPPER.php +++ b/tests/data/Calculation/TextData/UPPER.php @@ -29,4 +29,5 @@ 'FALSE', false, ], + 'no arguments' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/VALUE.php b/tests/data/Calculation/TextData/VALUE.php index d859c08703..666f12d102 100644 --- a/tests/data/Calculation/TextData/VALUE.php +++ b/tests/data/Calculation/TextData/VALUE.php @@ -41,4 +41,7 @@ '0.11527777777778', '2:46 AM', ], + 'no arguments' => ['exception'], + 'bool argument' => ['#VALUE!', false], + 'null argument' => ['0', null], ];