Skip to content

Commit 4f06d84

Browse files
authored
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.
1 parent 55b9520 commit 4f06d84

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1425
-559
lines changed

phpstan-baseline.neon

-95
Original file line numberDiff line numberDiff line change
@@ -1320,11 +1320,6 @@ parameters:
13201320
count: 1
13211321
path: src/PhpSpreadsheet/Calculation/TextData.php
13221322

1323-
-
1324-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:TRIMSPACES\\(\\) should return string but returns string\\|null\\.$#"
1325-
count: 1
1326-
path: src/PhpSpreadsheet/Calculation/TextData.php
1327-
13281323
-
13291324
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\:\\:CONCATENATE\\(\\) has parameter \\$args with no typehint specified\\.$#"
13301325
count: 1
@@ -1340,96 +1335,6 @@ parameters:
13401335
count: 1
13411336
path: src/PhpSpreadsheet/Calculation/TextData.php
13421337

1343-
-
1344-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:character\\(\\) should return string but returns string\\|false\\.$#"
1345-
count: 1
1346-
path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
1347-
1348-
-
1349-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:unicodeToOrd\\(\\) has no return typehint specified\\.$#"
1350-
count: 1
1351-
path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
1352-
1353-
-
1354-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:unicodeToOrd\\(\\) has parameter \\$character with no typehint specified\\.$#"
1355-
count: 1
1356-
path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
1357-
1358-
-
1359-
message: "#^Cannot access offset 1 on array\\|false\\.$#"
1360-
count: 1
1361-
path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
1362-
1363-
-
1364-
message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#"
1365-
count: 1
1366-
path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
1367-
1368-
-
1369-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:convertBooleanValue\\(\\) has no return typehint specified\\.$#"
1370-
count: 1
1371-
path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
1372-
1373-
-
1374-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\CharacterConvert\\:\\:convertBooleanValue\\(\\) has parameter \\$value with no typehint specified\\.$#"
1375-
count: 1
1376-
path: src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
1377-
1378-
-
1379-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Concatenate\\:\\:CONCATENATE\\(\\) has parameter \\$args with no typehint specified\\.$#"
1380-
count: 1
1381-
path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php
1382-
1383-
-
1384-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Concatenate\\:\\:convertBooleanValue\\(\\) has no return typehint specified\\.$#"
1385-
count: 1
1386-
path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php
1387-
1388-
-
1389-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Concatenate\\:\\:convertBooleanValue\\(\\) has parameter \\$value with no typehint specified\\.$#"
1390-
count: 1
1391-
path: src/PhpSpreadsheet/Calculation/TextData/Concatenate.php
1392-
1393-
-
1394-
message: "#^Parameter \\#3 \\$length of function mb_substr expects int\\|null, float\\|int\\<0, max\\>\\|string given\\.$#"
1395-
count: 3
1396-
path: src/PhpSpreadsheet/Calculation/TextData/Extract.php
1397-
1398-
-
1399-
message: "#^Parameter \\#2 \\$start of function mb_substr expects int, float\\|int given\\.$#"
1400-
count: 2
1401-
path: src/PhpSpreadsheet/Calculation/TextData/Extract.php
1402-
1403-
-
1404-
message: "#^Cannot cast array\\|float\\|int\\|string to float\\.$#"
1405-
count: 1
1406-
path: src/PhpSpreadsheet/Calculation/TextData/Format.php
1407-
1408-
-
1409-
message: "#^Parameter \\#3 \\$offset of function mb_strpos expects int, float\\|int given\\.$#"
1410-
count: 1
1411-
path: src/PhpSpreadsheet/Calculation/TextData/Search.php
1412-
1413-
-
1414-
message: "#^Parameter \\#3 \\$offset of function mb_stripos expects int, float\\|int given\\.$#"
1415-
count: 1
1416-
path: src/PhpSpreadsheet/Calculation/TextData/Search.php
1417-
1418-
-
1419-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\TextData\\\\Trim\\:\\:\\$invalidChars has no typehint specified\\.$#"
1420-
count: 1
1421-
path: src/PhpSpreadsheet/Calculation/TextData/Trim.php
1422-
1423-
-
1424-
message: "#^Parameter \\#1 \\$str of function trim expects string, float\\|int\\|string given\\.$#"
1425-
count: 2
1426-
path: src/PhpSpreadsheet/Calculation/TextData/Trim.php
1427-
1428-
-
1429-
message: "#^Parameter \\#1 \\$str of function trim expects string, string\\|null given\\.$#"
1430-
count: 1
1431-
path: src/PhpSpreadsheet/Calculation/TextData/Trim.php
1432-
14331338
-
14341339
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Token\\\\Stack\\:\\:getStackItem\\(\\) has no return typehint specified\\.$#"
14351340
count: 1

src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php

+3-13
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
44

5-
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
65
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
76
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
87

@@ -18,10 +17,7 @@ class CaseConvert
1817
public static function lower($mixedCaseValue): string
1918
{
2019
$mixedCaseValue = Functions::flattenSingleValue($mixedCaseValue);
21-
22-
if (is_bool($mixedCaseValue)) {
23-
$mixedCaseValue = ($mixedCaseValue === true) ? Calculation::getTRUE() : Calculation::getFALSE();
24-
}
20+
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
2521

2622
return StringHelper::strToLower($mixedCaseValue);
2723
}
@@ -36,10 +32,7 @@ public static function lower($mixedCaseValue): string
3632
public static function upper($mixedCaseValue): string
3733
{
3834
$mixedCaseValue = Functions::flattenSingleValue($mixedCaseValue);
39-
40-
if (is_bool($mixedCaseValue)) {
41-
$mixedCaseValue = ($mixedCaseValue === true) ? Calculation::getTRUE() : Calculation::getFALSE();
42-
}
35+
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
4336

4437
return StringHelper::strToUpper($mixedCaseValue);
4538
}
@@ -54,10 +47,7 @@ public static function upper($mixedCaseValue): string
5447
public static function proper($mixedCaseValue): string
5548
{
5649
$mixedCaseValue = Functions::flattenSingleValue($mixedCaseValue);
57-
58-
if (is_bool($mixedCaseValue)) {
59-
$mixedCaseValue = ($mixedCaseValue === true) ? Calculation::getTRUE() : Calculation::getFALSE();
60-
}
50+
$mixedCaseValue = Helpers::extractString($mixedCaseValue);
6151

6252
return StringHelper::strToTitle($mixedCaseValue);
6353
}

src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php

+18-26
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,40 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
44

5-
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
65
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
76

87
class CharacterConvert
98
{
109
/**
11-
* CHARACTER.
10+
* CHAR.
1211
*
1312
* @param mixed $character Integer Value to convert to its character representation
1413
*/
1514
public static function character($character): string
1615
{
17-
$character = Functions::flattenSingleValue($character);
18-
19-
if (!is_numeric($character)) {
20-
return Functions::VALUE();
21-
}
22-
23-
$character = (int) $character;
24-
if ($character < 1 || $character > 255) {
16+
$character = Helpers::validateInt($character);
17+
$min = Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE ? 0 : 1;
18+
if ($character < $min || $character > 255) {
2519
return Functions::VALUE();
2620
}
21+
$result = iconv('UCS-4LE', 'UTF-8', pack('V', $character));
2722

28-
return iconv('UCS-4LE', 'UTF-8', pack('V', $character));
23+
return ($result === false) ? '' : $result;
2924
}
3025

3126
/**
32-
* ASCIICODE.
27+
* CODE.
3328
*
3429
* @param mixed $characters String character to convert to its ASCII value
3530
*
3631
* @return int|string A string if arguments are invalid
3732
*/
3833
public static function code($characters)
3934
{
40-
if (($characters === null) || ($characters === '')) {
35+
$characters = Helpers::extractString($characters);
36+
if ($characters === '') {
4137
return Functions::VALUE();
4238
}
43-
$characters = Functions::flattenSingleValue($characters);
44-
if (is_bool($characters)) {
45-
$characters = self::convertBooleanValue($characters);
46-
}
4739

4840
$character = $characters;
4941
if (mb_strlen($characters, 'UTF-8') > 1) {
@@ -53,17 +45,17 @@ public static function code($characters)
5345
return self::unicodeToOrd($character);
5446
}
5547

56-
private static function unicodeToOrd($character)
57-
{
58-
return unpack('V', iconv('UTF-8', 'UCS-4LE', $character))[1];
59-
}
60-
61-
private static function convertBooleanValue($value)
48+
private static function unicodeToOrd(string $character): int
6249
{
63-
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
64-
return (int) $value;
50+
$retVal = 0;
51+
$iconv = iconv('UTF-8', 'UCS-4LE', $character);
52+
if ($iconv !== false) {
53+
$result = unpack('V', $iconv);
54+
if (is_array($result) && isset($result[1])) {
55+
$retVal = $result[1];
56+
}
6557
}
6658

67-
return ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
59+
return $retVal;
6860
}
6961
}

src/PhpSpreadsheet/Calculation/TextData/Concatenate.php

+8-19
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,24 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
44

5-
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
65
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
76

87
class Concatenate
98
{
109
/**
1110
* CONCATENATE.
11+
*
12+
* @param array $args
1213
*/
1314
public static function CONCATENATE(...$args): string
1415
{
1516
$returnValue = '';
1617

1718
// Loop through arguments
1819
$aArgs = Functions::flattenArray($args);
20+
1921
foreach ($aArgs as $arg) {
20-
if (is_bool($arg)) {
21-
$arg = self::convertBooleanValue($arg);
22-
}
23-
$returnValue .= $arg;
22+
$returnValue .= Helpers::extractString($arg);
2423
}
2524

2625
return $returnValue;
@@ -35,13 +34,15 @@ public static function CONCATENATE(...$args): string
3534
*/
3635
public static function TEXTJOIN($delimiter, $ignoreEmpty, ...$args): string
3736
{
37+
$delimiter = Functions::flattenSingleValue($delimiter);
38+
$ignoreEmpty = Functions::flattenSingleValue($ignoreEmpty);
3839
// Loop through arguments
3940
$aArgs = Functions::flattenArray($args);
4041
foreach ($aArgs as $key => &$arg) {
4142
if ($ignoreEmpty === true && is_string($arg) && trim($arg) === '') {
4243
unset($aArgs[$key]);
4344
} elseif (is_bool($arg)) {
44-
$arg = self::convertBooleanValue($arg);
45+
$arg = Helpers::convertBooleanValue($arg);
4546
}
4647
}
4748

@@ -59,24 +60,12 @@ public static function TEXTJOIN($delimiter, $ignoreEmpty, ...$args): string
5960
public static function builtinREPT($stringValue, $repeatCount): string
6061
{
6162
$repeatCount = Functions::flattenSingleValue($repeatCount);
63+
$stringValue = Helpers::extractString($stringValue);
6264

6365
if (!is_numeric($repeatCount) || $repeatCount < 0) {
6466
return Functions::VALUE();
6567
}
6668

67-
if (is_bool($stringValue)) {
68-
$stringValue = self::convertBooleanValue($stringValue);
69-
}
70-
7169
return str_repeat($stringValue, (int) $repeatCount);
7270
}
73-
74-
private static function convertBooleanValue($value)
75-
{
76-
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
77-
return (int) $value;
78-
}
79-
80-
return ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
81-
}
8271
}

src/PhpSpreadsheet/Calculation/TextData/Extract.php

+20-33
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
44

5-
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6-
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
5+
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
76

87
class Extract
98
{
@@ -13,17 +12,13 @@ class Extract
1312
* @param mixed $value String value from which to extract characters
1413
* @param mixed $chars The number of characters to extract (as an integer)
1514
*/
16-
public static function left($value = '', $chars = 1): string
15+
public static function left($value, $chars = 1): string
1716
{
18-
$value = Functions::flattenSingleValue($value);
19-
$chars = Functions::flattenSingleValue($chars);
20-
21-
if (!is_numeric($chars) || $chars < 0) {
22-
return Functions::VALUE();
23-
}
24-
25-
if (is_bool($value)) {
26-
$value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
17+
try {
18+
$value = Helpers::extractString($value);
19+
$chars = Helpers::extractInt($chars, 0, 1);
20+
} catch (CalcExp $e) {
21+
return $e->getMessage();
2722
}
2823

2924
return mb_substr($value ?? '', 0, $chars, 'UTF-8');
@@ -36,18 +31,14 @@ public static function left($value = '', $chars = 1): string
3631
* @param mixed $start Integer offset of the first character that we want to extract
3732
* @param mixed $chars The number of characters to extract (as an integer)
3833
*/
39-
public static function mid($value = '', $start = 1, $chars = null): string
34+
public static function mid($value, $start, $chars): string
4035
{
41-
$value = Functions::flattenSingleValue($value);
42-
$start = Functions::flattenSingleValue($start);
43-
$chars = Functions::flattenSingleValue($chars);
44-
45-
if (!is_numeric($start) || $start < 1 || !is_numeric($chars) || $chars < 0) {
46-
return Functions::VALUE();
47-
}
48-
49-
if (is_bool($value)) {
50-
$value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
36+
try {
37+
$value = Helpers::extractString($value);
38+
$start = Helpers::extractInt($start, 1);
39+
$chars = Helpers::extractInt($chars, 0);
40+
} catch (CalcExp $e) {
41+
return $e->getMessage();
5142
}
5243

5344
return mb_substr($value ?? '', --$start, $chars, 'UTF-8');
@@ -59,17 +50,13 @@ public static function mid($value = '', $start = 1, $chars = null): string
5950
* @param mixed $value String value from which to extract characters
6051
* @param mixed $chars The number of characters to extract (as an integer)
6152
*/
62-
public static function right($value = '', $chars = 1): string
53+
public static function right($value, $chars = 1): string
6354
{
64-
$value = Functions::flattenSingleValue($value);
65-
$chars = Functions::flattenSingleValue($chars);
66-
67-
if (!is_numeric($chars) || $chars < 0) {
68-
return Functions::VALUE();
69-
}
70-
71-
if (is_bool($value)) {
72-
$value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
55+
try {
56+
$value = Helpers::extractString($value);
57+
$chars = Helpers::extractInt($chars, 0, 1);
58+
} catch (CalcExp $e) {
59+
return $e->getMessage();
7360
}
7461

7562
return mb_substr($value ?? '', mb_strlen($value ?? '', 'UTF-8') - $chars, $chars, 'UTF-8');

0 commit comments

Comments
 (0)