From 4f06d8424805e1ef00985128c522f3b2c1f017ca Mon Sep 17 00:00:00 2001 From: oleibman Date: Mon, 14 Jun 2021 23:37:17 -0700 Subject: [PATCH] TextData - Minor Changes, Test Coverage (#2151) * PHP8.1 Deprecation Passing Null to String Function For each of the files in this PR, one or more statements can pass a null to string functions like strlower. This is deprecated in PHP8.1, and, when deprecated messages are enabled, causes many tests to error out. In every case, use coercion to pass null string rather than null. * 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. * Scrutinizer Correct 3 minor errors. --- phpstan-baseline.neon | 95 ---------- .../Calculation/TextData/CaseConvert.php | 16 +- .../Calculation/TextData/CharacterConvert.php | 44 ++--- .../Calculation/TextData/Concatenate.php | 27 +-- .../Calculation/TextData/Extract.php | 53 +++--- .../Calculation/TextData/Format.php | 100 +++++++---- .../Calculation/TextData/Helpers.php | 90 ++++++++++ .../Calculation/TextData/Replace.php | 43 +++-- .../Calculation/TextData/Search.php | 62 +++---- .../Calculation/TextData/Text.php | 15 +- .../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 | 167 ++++++++++++++++-- .../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, 1425 insertions(+), 559 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 5986450cf..8a7856b54 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 36b5efbd6..664cc2d88 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 @@ class CaseConvert 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 @@ class CaseConvert 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 4397b538b..0aca57e6c 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)) { + $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)); - $character = (int) $character; - if ($character < 1 || $character > 255) { - return Functions::VALUE(); - } - - 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 @@ class CharacterConvert */ 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 @@ class CharacterConvert return self::unicodeToOrd($character); } - private static function unicodeToOrd($character) + private static function unicodeToOrd(string $character): int { - return unpack('V', iconv('UTF-8', 'UCS-4LE', $character))[1]; - } - - private static function convertBooleanValue($value) - { - 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 5780bb6e2..d53fc8228 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 @@ class Concatenate // 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 @@ class Concatenate */ 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 @@ class Concatenate 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 015fabfb8..7f18e0c6e 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,17 +12,13 @@ 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'); @@ -36,18 +31,14 @@ class Extract * @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(); - } - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); + try { + $value = Helpers::extractString($value); + $start = Helpers::extractInt($start, 1); + $chars = Helpers::extractInt($chars, 0); + } catch (CalcExp $e) { + return $e->getMessage(); } return mb_substr($value ?? '', --$start, $chars, 'UTF-8'); @@ -59,17 +50,13 @@ 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 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'); diff --git a/src/PhpSpreadsheet/Calculation/TextData/Format.php b/src/PhpSpreadsheet/Calculation/TextData/Format.php index 6bad17441..3286de0cb 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Format.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Format.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData; 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 @@ class Format } /** - * 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 @@ class Format */ 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 @@ class Format } /** - * 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 @@ class Format */ 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 @@ class Format 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 @@ class Format */ 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 @@ class Format } } - 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 000000000..423b6d3f9 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/TextData/Helpers.php @@ -0,0 +1,90 @@ +getMessage(); + } return $left . $newText . $right; } @@ -35,15 +40,25 @@ class Replace * @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 2da688d88..c9eed2e5a 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 @@ class Search } /** - * 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 @@ class Search */ 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 f3da4bb33..6f8253eab 100644 --- a/src/PhpSpreadsheet/Calculation/TextData/Text.php +++ b/src/PhpSpreadsheet/Calculation/TextData/Text.php @@ -2,23 +2,18 @@ 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); - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } + $value = Helpers::extractString($value); return mb_strlen($value ?? '', 'UTF-8'); } @@ -33,10 +28,10 @@ class Text */ 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 b5d664554..22f4554b3 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); + $stringValue = Helpers::extractString($stringValue); - if (is_bool($stringValue)) { - return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (self::$invalidChars === null) { - self::$invalidChars = range(chr(0), chr(31)); - } - - 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(); - } + $stringValue = Helpers::extractString($stringValue); - if (is_string($stringValue) || is_numeric($stringValue)) { - return trim(preg_replace('/ +/', ' ', trim($stringValue, ' ')), ' '); - } - - return null; + return trim(preg_replace('/ +/', ' ', trim("$stringValue", ' ')) ?? '', ' '); } } diff --git a/src/PhpSpreadsheet/Settings.php b/src/PhpSpreadsheet/Settings.php index 8fdccbad1..b51cd9d0b 100644 --- a/src/PhpSpreadsheet/Settings.php +++ b/src/PhpSpreadsheet/Settings.php @@ -68,6 +68,11 @@ class Settings 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 493fd07e4..99f3cfb09 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -1234,6 +1234,7 @@ class Worksheet extends WriterPart 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 000000000..e0343b2b8 --- /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 000000000..3caadcb53 --- /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 e8934ce53..66d01cb08 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 e3ad55449..03939882d 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 6f0f6b065..52baf1a11 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 88ad6bfd7..6c9a871de 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 000000000..49ac421b8 --- /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 e856832f4..fc0f481e1 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 3631c6a7b..14e48b7a0 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 ab3cfb7a7..a6b6e1970 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 c2508dfd6..37ea2f3dc 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 @@ class LeftTest extends TestCase { $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 @@ class LeftTest extends TestCase ['ЛОЖ', '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 7ab36b677..b2ce124e1 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 1d11f1e56..4639624a2 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 @@ class LowerTest extends TestCase { $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 267d68945..a1eebf47f 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 @@ class MidTest extends TestCase * @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,125 @@ class MidTest extends TestCase ['ОЖ', '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()); + self::assertEquals($resultB3, $sheet->getCell('B3')->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 f54bfc3d9..86d8ca8b9 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 000000000..7421e41ae --- /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 8366b0705..51968d83f 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 @@ class ProperTest extends TestCase { $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 781e454cb..0e1208642 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 a0c089cf6..ea86d1b60 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 + public function testReptThroughEngine($expectedResult, $val = 'omitted', $rpt = 'omitted'): 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 - { - 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 a2ab3a4c3..b9051d0da 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 @@ class RightTest extends TestCase { $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 @@ class RightTest extends TestCase ['ЖЬ', '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 14f5735ba..5f1c526d1 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 792e1a152..bbb055912 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 78e72f967..60b21661c 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 914180652..3ede2ffe9 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 302dce4f9..133e850f6 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 45f7aea88..0147b5c18 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 @@ class UpperTest extends TestCase { $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 b3ba1246c..fbef254b4 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 @@ class ValueTest extends TestCase protected function tearDown(): void { + parent::tearDown(); StringHelper::setCurrencyCode($this->currencyCode); StringHelper::setDecimalSeparator($this->decimalSeparator); StringHelper::setThousandsSeparator($this->thousandsSeparator); @@ -43,13 +43,21 @@ class ValueTest extends TestCase * @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 276d70151..f02437b7d 100644 --- a/tests/data/Calculation/TextData/CHAR.php +++ b/tests/data/Calculation/TextData/CHAR.php @@ -61,4 +61,10 @@ return [ '#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 676088832..ad668271a 100644 --- a/tests/data/Calculation/TextData/CLEAN.php +++ b/tests/data/Calculation/TextData/CLEAN.php @@ -25,4 +25,5 @@ return [ null, null, ], + 'omitted argument' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/CODE.php b/tests/data/Calculation/TextData/CODE.php index 1b74b6991..8ab927263 100644 --- a/tests/data/Calculation/TextData/CODE.php +++ b/tests/data/Calculation/TextData/CODE.php @@ -69,4 +69,5 @@ return [ 0x2020, '†', ], + 'omitted argument' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/CONCATENATE.php b/tests/data/Calculation/TextData/CONCATENATE.php index f17b024bb..c6b583eb2 100644 --- a/tests/data/Calculation/TextData/CONCATENATE.php +++ b/tests/data/Calculation/TextData/CONCATENATE.php @@ -18,4 +18,5 @@ return [ '-', true, ], + 'no arguments' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/DOLLAR.php b/tests/data/Calculation/TextData/DOLLAR.php index 205527b8d..78fcc1b52 100644 --- a/tests/data/Calculation/TextData/DOLLAR.php +++ b/tests/data/Calculation/TextData/DOLLAR.php @@ -45,4 +45,10 @@ return [ 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 f760a58fb..f392b0b05 100644 --- a/tests/data/Calculation/TextData/EXACT.php +++ b/tests/data/Calculation/TextData/EXACT.php @@ -36,4 +36,6 @@ return [ ' ', '', ], + 'no arguments' => ['exception'], + 'one argument1' => ['exception', 'abc'], ]; diff --git a/tests/data/Calculation/TextData/FIND.php b/tests/data/Calculation/TextData/FIND.php index 04d3276d4..1ae6d730e 100644 --- a/tests/data/Calculation/TextData/FIND.php +++ b/tests/data/Calculation/TextData/FIND.php @@ -106,4 +106,6 @@ return [ 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 2083ecceb..29734404c 100644 --- a/tests/data/Calculation/TextData/FIXED.php +++ b/tests/data/Calculation/TextData/FIXED.php @@ -59,4 +59,9 @@ return [ '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 d524dc36f..17d3fcf7a 100644 --- a/tests/data/Calculation/TextData/LEFT.php +++ b/tests/data/Calculation/TextData/LEFT.php @@ -26,11 +26,15 @@ return [ 'QWERTYUIOP', 'NaN', ], - [ - '#VALUE!', + 'null length defaults to 0' => [ + '', 'QWERTYUIOP', null, ], + 'omitted length defaults to 1' => [ + 'Q', + 'QWERTYUIOP', + ], [ 'ABC', 'ABCDEFGHI', @@ -61,4 +65,7 @@ return [ false, 2, ], + 'string not specified' => [ + 'exception', + ], ]; diff --git a/tests/data/Calculation/TextData/LEN.php b/tests/data/Calculation/TextData/LEN.php index 6bf44fc9e..babf3535a 100644 --- a/tests/data/Calculation/TextData/LEN.php +++ b/tests/data/Calculation/TextData/LEN.php @@ -25,4 +25,5 @@ return [ 5, false, ], + 'no arguments' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/LOWER.php b/tests/data/Calculation/TextData/LOWER.php index c5360b959..23825546d 100644 --- a/tests/data/Calculation/TextData/LOWER.php +++ b/tests/data/Calculation/TextData/LOWER.php @@ -29,4 +29,5 @@ return [ 'false', false, ], + 'no argument' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/MID.php b/tests/data/Calculation/TextData/MID.php index b434f6704..45e0ceab9 100644 --- a/tests/data/Calculation/TextData/MID.php +++ b/tests/data/Calculation/TextData/MID.php @@ -37,11 +37,24 @@ return [ 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 3a6510b84..791e61cf8 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 000000000..fc79d5f11 --- /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 8bbf0e5c6..e9a5d4ceb 100644 --- a/tests/data/Calculation/TextData/PROPER.php +++ b/tests/data/Calculation/TextData/PROPER.php @@ -25,4 +25,5 @@ return [ 'False', false, ], + 'no argument' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/REPLACE.php b/tests/data/Calculation/TextData/REPLACE.php index 09e229682..8f02e2e36 100644 --- a/tests/data/Calculation/TextData/REPLACE.php +++ b/tests/data/Calculation/TextData/REPLACE.php @@ -57,4 +57,12 @@ return [ 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 2c8d1c0d5..f8aef72c8 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 e6928df22..77ea64bf7 100644 --- a/tests/data/Calculation/TextData/RIGHT.php +++ b/tests/data/Calculation/TextData/RIGHT.php @@ -21,11 +21,15 @@ return [ 'QWERTYUIOP', 'NaN', ], - [ - '#VALUE!', + 'null length defaults to 0' => [ + '', 'QWERTYUIOP', null, ], + 'omitted length defaults to 1' => [ + 'P', + 'QWERTYUIOP', + ], [ 'GHI', 'ABCDEFGHI', @@ -61,4 +65,7 @@ return [ false, 2, ], + 'string not specified' => [ + 'exception', + ], ]; diff --git a/tests/data/Calculation/TextData/SEARCH.php b/tests/data/Calculation/TextData/SEARCH.php index fa970bec4..335a7f12a 100644 --- a/tests/data/Calculation/TextData/SEARCH.php +++ b/tests/data/Calculation/TextData/SEARCH.php @@ -99,4 +99,6 @@ return [ 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 97cb8d0ff..9b695a715 100644 --- a/tests/data/Calculation/TextData/SUBSTITUTE.php +++ b/tests/data/Calculation/TextData/SUBSTITUTE.php @@ -73,4 +73,16 @@ return [ "\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 fcb05191a..1c7c338ad 100644 --- a/tests/data/Calculation/TextData/TEXT.php +++ b/tests/data/Calculation/TextData/TEXT.php @@ -66,4 +66,7 @@ return [ 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 9c6b42468..e345f7c17 100644 --- a/tests/data/Calculation/TextData/TEXTJOIN.php +++ b/tests/data/Calculation/TextData/TEXTJOIN.php @@ -37,4 +37,9 @@ return [ '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 f400f1872..882dc824d 100644 --- a/tests/data/Calculation/TextData/TRIM.php +++ b/tests/data/Calculation/TextData/TRIM.php @@ -29,4 +29,5 @@ return [ null, null, ], + 'no arguments' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/UPPER.php b/tests/data/Calculation/TextData/UPPER.php index e5d2f18ea..21b04ae46 100644 --- a/tests/data/Calculation/TextData/UPPER.php +++ b/tests/data/Calculation/TextData/UPPER.php @@ -29,4 +29,5 @@ return [ 'FALSE', false, ], + 'no arguments' => ['exception'], ]; diff --git a/tests/data/Calculation/TextData/VALUE.php b/tests/data/Calculation/TextData/VALUE.php index d859c0870..666f12d10 100644 --- a/tests/data/Calculation/TextData/VALUE.php +++ b/tests/data/Calculation/TextData/VALUE.php @@ -41,4 +41,7 @@ return [ '0.11527777777778', '2:46 AM', ], + 'no arguments' => ['exception'], + 'bool argument' => ['#VALUE!', false], + 'null argument' => ['0', null], ];