Skip to content

Commit 23e2d70

Browse files
authored
Merge branch 'master' into Xls-Reader-Conditional-Formatting
2 parents be8c444 + 1bd5369 commit 23e2d70

File tree

16 files changed

+686
-113
lines changed

16 files changed

+686
-113
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org).
99

1010
### Added
1111

12-
- Implementation of the ISREF() information function.
12+
- Implementation of the UNIQUE() Lookup/Reference (array) function
13+
- Implementation of the ISREF() Information function.
1314
- Added support for reading "formatted" numeric values from Csv files; although default behaviour of reading these values as strings is preserved.
1415

1516
(i.e a value of "12,345.67" can be read as numeric `1235.67`, not simply as a string `"12,345.67"`, if the `castFormattedNumberToNumeric()` setting is enabled.
@@ -31,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3132
This is determined by the Calculation Engine locale setting.
3233

3334
(i.e. `"Vrai"` wil be converted to a boolean `true` if the Locale is set to `fr`.)
35+
- Allow `psr/simple-cache` 2.x
3436

3537
### Deprecated
3638

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
"markbaker/matrix": "^3.0",
7676
"psr/http-client": "^1.0",
7777
"psr/http-factory": "^1.0",
78-
"psr/simple-cache": "^1.0"
78+
"psr/simple-cache": "^1.0 || ^2.0"
7979
},
8080
"require-dev": {
8181
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",

composer.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

phpstan-baseline.neon

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2300,16 +2300,6 @@ parameters:
23002300
count: 2
23012301
path: src/PhpSpreadsheet/Reader/Xls.php
23022302

2303-
-
2304-
message: "#^Parameter \\#1 \\$errorStyle of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setErrorStyle\\(\\) expects string, int\\|string given\\.$#"
2305-
count: 1
2306-
path: src/PhpSpreadsheet/Reader/Xls.php
2307-
2308-
-
2309-
message: "#^Parameter \\#1 \\$operator of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setOperator\\(\\) expects string, int\\|string given\\.$#"
2310-
count: 1
2311-
path: src/PhpSpreadsheet/Reader/Xls.php
2312-
23132303
-
23142304
message: "#^Parameter \\#1 \\$showSummaryBelow of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:setShowSummaryBelow\\(\\) expects bool, int given\\.$#"
23152305
count: 1
@@ -2320,11 +2310,6 @@ parameters:
23202310
count: 1
23212311
path: src/PhpSpreadsheet/Reader/Xls.php
23222312

2323-
-
2324-
message: "#^Parameter \\#1 \\$type of method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\DataValidation\\:\\:setType\\(\\) expects string, int\\|string given\\.$#"
2325-
count: 1
2326-
path: src/PhpSpreadsheet/Reader/Xls.php
2327-
23282313
-
23292314
message: "#^Parameter \\#2 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\IReadFilter\\:\\:readCell\\(\\) expects int, string given\\.$#"
23302315
count: 1
@@ -5484,3 +5469,4 @@ parameters:
54845469
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xlsx\\\\Xlfn\\:\\:addXlfn\\(\\) should return string but returns string\\|null\\.$#"
54855470
count: 1
54865471
path: src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php
5472+

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2583,7 +2583,7 @@ class Calculation
25832583
],
25842584
'UNIQUE' => [
25852585
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
2586-
'functionCall' => [Functions::class, 'DUMMY'],
2586+
'functionCall' => [LookupRef\Unique::class, 'unique'],
25872587
'argumentCount' => '1+',
25882588
],
25892589
'UPPER' => [

src/PhpSpreadsheet/Calculation/Information/ErrorValue.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public static function isError($value = '')
4747
return false;
4848
}
4949

50-
return in_array($value, ExcelError::$errorCodes);
50+
return in_array($value, ExcelError::$errorCodes) || $value === ExcelError::CALC();
5151
}
5252

5353
/**

src/PhpSpreadsheet/Calculation/Information/ExcelError.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class ExcelError
2222
'num' => '#NUM!',
2323
'na' => '#N/A',
2424
'gettingdata' => '#GETTING_DATA',
25+
'spill' => '#SPILL!',
2526
];
2627

2728
/**
@@ -45,6 +46,10 @@ public static function type($value = '')
4546
++$i;
4647
}
4748

49+
if ($value === self::CALC()) {
50+
return 14;
51+
}
52+
4853
return self::NA();
4954
}
5055

@@ -127,10 +132,20 @@ public static function NAME()
127132
/**
128133
* DIV0.
129134
*
130-
* @return string #Not Yet Implemented
135+
* @return string #DIV/0!
131136
*/
132137
public static function DIV0()
133138
{
134139
return self::$errorCodes['divisionbyzero'];
135140
}
141+
142+
/**
143+
* CALC.
144+
*
145+
* @return string #Not Yet Implemented
146+
*/
147+
public static function CALC()
148+
{
149+
return '#CALC!';
150+
}
136151
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
4+
5+
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
6+
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
7+
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
8+
9+
class Unique
10+
{
11+
/**
12+
* UNIQUE
13+
* The UNIQUE function searches for value either from a one-row or one-column range or from an array.
14+
*
15+
* @param mixed $lookupVector The range of cells being searched
16+
* @param mixed $byColumn Whether the uniqueness should be determined by row (the default) or by column
17+
* @param mixed $exactlyOnce Whether the function should return only entries that occur just once in the list
18+
*
19+
* @return mixed The unique values from the search range
20+
*/
21+
public static function unique($lookupVector, $byColumn = false, $exactlyOnce = false)
22+
{
23+
if (!is_array($lookupVector)) {
24+
// Scalars are always returned "as is"
25+
return $lookupVector;
26+
}
27+
28+
$byColumn = (bool) $byColumn;
29+
$exactlyOnce = (bool) $exactlyOnce;
30+
31+
return ($byColumn === true)
32+
? self::uniqueByColumn($lookupVector, $exactlyOnce)
33+
: self::uniqueByRow($lookupVector, $exactlyOnce);
34+
}
35+
36+
/**
37+
* @return mixed
38+
*/
39+
private static function uniqueByRow(array $lookupVector, bool $exactlyOnce)
40+
{
41+
// When not $byColumn, we count whole rows or values, not individual values
42+
// so implode each row into a single string value
43+
array_walk(
44+
$lookupVector,
45+
function (array &$value): void {
46+
$value = implode(chr(0x00), $value);
47+
}
48+
);
49+
50+
$result = self::countValuesCaseInsensitive($lookupVector);
51+
52+
if ($exactlyOnce === true) {
53+
$result = self::exactlyOnceFilter($result);
54+
}
55+
56+
if (count($result) === 0) {
57+
return ExcelError::CALC();
58+
}
59+
60+
$result = array_keys($result);
61+
62+
// restore rows from their strings
63+
array_walk(
64+
$result,
65+
function (string &$value): void {
66+
$value = explode(chr(0x00), $value);
67+
}
68+
);
69+
70+
return (count($result) === 1) ? array_pop($result) : $result;
71+
}
72+
73+
/**
74+
* @return mixed
75+
*/
76+
private static function uniqueByColumn(array $lookupVector, bool $exactlyOnce)
77+
{
78+
$flattenedLookupVector = Functions::flattenArray($lookupVector);
79+
80+
if (count($lookupVector, COUNT_RECURSIVE) > count($flattenedLookupVector, COUNT_RECURSIVE) + 1) {
81+
// We're looking at a full column check (multiple rows)
82+
$transpose = Matrix::transpose($lookupVector);
83+
$result = self::uniqueByRow($transpose, $exactlyOnce);
84+
85+
return (is_array($result)) ? Matrix::transpose($result) : $result;
86+
}
87+
88+
$result = self::countValuesCaseInsensitive($flattenedLookupVector);
89+
90+
if ($exactlyOnce === true) {
91+
$result = self::exactlyOnceFilter($result);
92+
}
93+
94+
if (count($result) === 0) {
95+
return ExcelError::CALC();
96+
}
97+
98+
$result = array_keys($result);
99+
100+
return $result;
101+
}
102+
103+
private static function countValuesCaseInsensitive(array $caseSensitiveLookupValues): array
104+
{
105+
$caseInsensitiveCounts = array_count_values(
106+
array_map(
107+
function (string $value) {
108+
return StringHelper::strToUpper($value);
109+
},
110+
$caseSensitiveLookupValues
111+
)
112+
);
113+
114+
$caseSensitiveCounts = [];
115+
foreach ($caseInsensitiveCounts as $caseInsensitiveKey => $count) {
116+
if (is_numeric($caseInsensitiveKey)) {
117+
$caseSensitiveCounts[$caseInsensitiveKey] = $count;
118+
} else {
119+
foreach ($caseSensitiveLookupValues as $caseSensitiveValue) {
120+
if ($caseInsensitiveKey === StringHelper::strToUpper($caseSensitiveValue)) {
121+
$caseSensitiveCounts[$caseSensitiveValue] = $count;
122+
123+
break;
124+
}
125+
}
126+
}
127+
}
128+
129+
return $caseSensitiveCounts;
130+
}
131+
132+
private static function exactlyOnceFilter(array $values): array
133+
{
134+
return array_filter(
135+
$values,
136+
function ($value) {
137+
return $value === 1;
138+
}
139+
);
140+
}
141+
}

src/PhpSpreadsheet/Reader/Xls.php

Lines changed: 5 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4788,57 +4788,11 @@ private function readDataValidation(): void
47884788

47894789
// bit: 0-3; mask: 0x0000000F; type
47904790
$type = (0x0000000F & $options) >> 0;
4791-
switch ($type) {
4792-
case 0x00:
4793-
$type = DataValidation::TYPE_NONE;
4794-
4795-
break;
4796-
case 0x01:
4797-
$type = DataValidation::TYPE_WHOLE;
4798-
4799-
break;
4800-
case 0x02:
4801-
$type = DataValidation::TYPE_DECIMAL;
4802-
4803-
break;
4804-
case 0x03:
4805-
$type = DataValidation::TYPE_LIST;
4806-
4807-
break;
4808-
case 0x04:
4809-
$type = DataValidation::TYPE_DATE;
4810-
4811-
break;
4812-
case 0x05:
4813-
$type = DataValidation::TYPE_TIME;
4814-
4815-
break;
4816-
case 0x06:
4817-
$type = DataValidation::TYPE_TEXTLENGTH;
4818-
4819-
break;
4820-
case 0x07:
4821-
$type = DataValidation::TYPE_CUSTOM;
4822-
4823-
break;
4824-
}
4791+
$type = Xls\DataValidationHelper::type($type);
48254792

48264793
// bit: 4-6; mask: 0x00000070; error type
48274794
$errorStyle = (0x00000070 & $options) >> 4;
4828-
switch ($errorStyle) {
4829-
case 0x00:
4830-
$errorStyle = DataValidation::STYLE_STOP;
4831-
4832-
break;
4833-
case 0x01:
4834-
$errorStyle = DataValidation::STYLE_WARNING;
4835-
4836-
break;
4837-
case 0x02:
4838-
$errorStyle = DataValidation::STYLE_INFORMATION;
4839-
4840-
break;
4841-
}
4795+
$errorStyle = Xls\DataValidationHelper::errorStyle($errorStyle);
48424796

48434797
// bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
48444798
// I have only seen cases where this is 1
@@ -4858,39 +4812,10 @@ private function readDataValidation(): void
48584812

48594813
// bit: 20-23; mask: 0x00F00000; condition operator
48604814
$operator = (0x00F00000 & $options) >> 20;
4861-
switch ($operator) {
4862-
case 0x00:
4863-
$operator = DataValidation::OPERATOR_BETWEEN;
4864-
4865-
break;
4866-
case 0x01:
4867-
$operator = DataValidation::OPERATOR_NOTBETWEEN;
4868-
4869-
break;
4870-
case 0x02:
4871-
$operator = DataValidation::OPERATOR_EQUAL;
4872-
4873-
break;
4874-
case 0x03:
4875-
$operator = DataValidation::OPERATOR_NOTEQUAL;
4815+
$operator = Xls\DataValidationHelper::operator($operator);
48764816

4877-
break;
4878-
case 0x04:
4879-
$operator = DataValidation::OPERATOR_GREATERTHAN;
4880-
4881-
break;
4882-
case 0x05:
4883-
$operator = DataValidation::OPERATOR_LESSTHAN;
4884-
4885-
break;
4886-
case 0x06:
4887-
$operator = DataValidation::OPERATOR_GREATERTHANOREQUAL;
4888-
4889-
break;
4890-
case 0x07:
4891-
$operator = DataValidation::OPERATOR_LESSTHANOREQUAL;
4892-
4893-
break;
4817+
if ($type === null || $errorStyle === null || $operator === null) {
4818+
return;
48944819
}
48954820

48964821
// offset: 4; size: var; title of the prompt box

0 commit comments

Comments
 (0)