Skip to content

Commit 483ef53

Browse files
author
MarkBaker
committed
Basic unit tests for formula parsing, in preparation for work on fully supporting the Union Operator, and for providing support for Structured References
1 parent 709e2ae commit 483ef53

File tree

2 files changed

+143
-8
lines changed

2 files changed

+143
-8
lines changed

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ class Calculation
108108
'+' => true, '-' => true, '*' => true, '/' => true,
109109
'^' => true, '&' => true, '%' => false, '~' => false,
110110
'>' => true, '<' => true, '=' => true, '>=' => true,
111-
'<=' => true, '<>' => true, '|' => true, ':' => true,
111+
'<=' => true, '<>' => true, '' => true, '' => true,
112+
':' => true,
112113
];
113114

114115
/**
@@ -120,7 +121,7 @@ class Calculation
120121
'+' => true, '-' => true, '*' => true, '/' => true,
121122
'^' => true, '&' => true, '>' => true, '<' => true,
122123
'=' => true, '>=' => true, '<=' => true, '<>' => true,
123-
'|' => true, ':' => true,
124+
'' => true, '' => true, ':' => true,
124125
];
125126

126127
/**
@@ -3872,7 +3873,7 @@ private function convertMatrixReferences($formula)
38723873
'*' => 0, '/' => 0, // Multiplication and Division
38733874
'+' => 0, '-' => 0, // Addition and Subtraction
38743875
'&' => 0, // Concatenation
3875-
'|' => 0, ':' => 0, // Intersect and Range
3876+
'' => 0, '' => 0, ':' => 0, // Union, Intersect and Range
38763877
'>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison
38773878
];
38783879

@@ -3884,8 +3885,9 @@ private function convertMatrixReferences($formula)
38843885
// This list includes all valid operators, whether binary (including boolean) or unary (such as %)
38853886
// Array key is the operator, the value is its precedence
38863887
private static $operatorPrecedence = [
3887-
':' => 8, // Range
3888-
'|' => 7, // Intersect
3888+
':' => 9, // Range
3889+
'' => 8, // Intersect
3890+
'' => 7, // Union
38893891
'~' => 6, // Negation
38903892
'%' => 5, // Percentage
38913893
'^' => 4, // Exponentiation
@@ -3957,7 +3959,7 @@ private function internalParseFormula($formula, ?Cell $cell = null)
39573959
++$index;
39583960
} elseif ($opCharacter == '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded?
39593961
++$index; // Drop the redundant plus symbol
3960-
} elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde or pipe, because they are legal
3962+
} elseif ((($opCharacter == '~') || ($opCharacter == '') || ($opCharacter == '')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde, union or intersect because they are legal
39613963
return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression
39623964
} elseif ((isset(self::$operators[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack?
39633965
while (
@@ -4338,7 +4340,7 @@ private function internalParseFormula($formula, ?Cell $cell = null)
43384340
) {
43394341
$output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
43404342
}
4341-
$stack->push('Binary Operator', '|'); // Put an Intersect Operator on the stack
4343+
$stack->push('Binary Operator', ''); // Put an Intersect Operator on the stack
43424344
$expectingOperator = false;
43434345
}
43444346
}
@@ -4638,7 +4640,7 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $cell = null)
46384640
}
46394641

46404642
break;
4641-
case '|': // Intersect
4643+
case '': // Intersect
46424644
$rowIntersect = array_intersect_key($operand1, $operand2);
46434645
$cellIntersect = $oCol = $oRow = [];
46444646
foreach (array_keys($rowIntersect) as $row) {
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Calculation;
4+
5+
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class ParseFormulaTest extends TestCase
9+
{
10+
/**
11+
* @dataProvider providerBinaryOperations
12+
*/
13+
public function testParseOperations(array $expectedStack, string $formula): void
14+
{
15+
$parser = Calculation::getInstance();
16+
$stack = $parser->parseFormula($formula);
17+
self::assertSame($expectedStack, $stack);
18+
}
19+
20+
public function providerBinaryOperations(): array
21+
{
22+
return [
23+
'Unary negative with Value' => [
24+
[
25+
['type' => 'Value', 'value' => 3, 'reference' => null],
26+
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
27+
],
28+
'=-3',
29+
],
30+
'Unary negative percentage with Value' => [
31+
[
32+
['type' => 'Value', 'value' => 3, 'reference' => null],
33+
['type' => 'Unary Operator', 'value' => '%', 'reference' => null],
34+
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
35+
],
36+
'=-3%',
37+
],
38+
'Binary minus with Values' => [
39+
[
40+
['type' => 'Value', 'value' => 3, 'reference' => null],
41+
['type' => 'Value', 'value' => 4, 'reference' => null],
42+
['type' => 'Binary Operator', 'value' => '-', 'reference' => null],
43+
],
44+
'=3-4',
45+
],
46+
'Unary negative with Cell Reference' => [
47+
[
48+
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
49+
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
50+
],
51+
'=-A1',
52+
],
53+
'Unary negative with FQ Cell Reference' => [
54+
[
55+
['type' => 'Cell Reference', 'value' => "'Sheet 1'!A1", 'reference' => "'Sheet 1'!A1"],
56+
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
57+
],
58+
"=-'Sheet 1'!A1",
59+
],
60+
'Unary negative percentage with Cell Reference' => [
61+
[
62+
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
63+
['type' => 'Unary Operator', 'value' => '%', 'reference' => null],
64+
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
65+
],
66+
'=-A1%',
67+
],
68+
'Unary negative with Defined Name' => [
69+
[
70+
['type' => 'Defined Name', 'value' => 'DEFINED_NAME', 'reference' => 'DEFINED_NAME'],
71+
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
72+
],
73+
'=-DEFINED_NAME',
74+
],
75+
'Unary negative percentage with Defined Name' => [
76+
[
77+
['type' => 'Defined Name', 'value' => 'DEFINED_NAME', 'reference' => 'DEFINED_NAME'],
78+
['type' => 'Unary Operator', 'value' => '%', 'reference' => null],
79+
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
80+
],
81+
'=-DEFINED_NAME%',
82+
],
83+
'Cell Range' => [
84+
[
85+
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
86+
['type' => 'Cell Reference', 'value' => 'C3', 'reference' => 'C3'],
87+
['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
88+
],
89+
'=A1:C3',
90+
],
91+
'Cell Range Intersection' => [
92+
[
93+
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
94+
['type' => 'Cell Reference', 'value' => 'C3', 'reference' => 'C3'],
95+
['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
96+
['type' => 'Cell Reference', 'value' => 'B2', 'reference' => 'B2'],
97+
['type' => 'Cell Reference', 'value' => 'D4', 'reference' => 'D4'],
98+
['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
99+
['type' => 'Binary Operator', 'value' => '', 'reference' => null],
100+
],
101+
'=A1:C3 B2:D4',
102+
],
103+
'Named Range Intersection' => [
104+
[
105+
['type' => 'Defined Name', 'value' => 'DEFINED_NAME_1', 'reference' => 'DEFINED_NAME_1'],
106+
['type' => 'Defined Name', 'value' => 'DEFINED_NAME_2', 'reference' => 'DEFINED_NAME_2'],
107+
['type' => 'Binary Operator', 'value' => '', 'reference' => null],
108+
],
109+
'=DEFINED_NAME_1 DEFINED_NAME_2',
110+
],
111+
// 'Cell Range Union' => [
112+
// [
113+
// ['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
114+
// ['type' => 'Cell Reference', 'value' => 'C3', 'reference' => 'C3'],
115+
// ['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
116+
// ['type' => 'Cell Reference', 'value' => 'B2', 'reference' => 'B2'],
117+
// ['type' => 'Cell Reference', 'value' => 'D4', 'reference' => 'D4'],
118+
// ['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
119+
// ['type' => 'Binary Operator', 'value' => '∪', 'reference' => null],
120+
// ],
121+
// '=A1:C3,B2:D4',
122+
// ],
123+
// 'Named Range Union' => [
124+
// [
125+
// ['type' => 'Defined Name', 'value' => 'DEFINED_NAME_1', 'reference' => 'DEFINED_NAME_1'],
126+
// ['type' => 'Defined Name', 'value' => 'DEFINED_NAME_2', 'reference' => 'DEFINED_NAME_2'],
127+
// ['type' => 'Binary Operator', 'value' => '∪', 'reference' => null],
128+
// ],
129+
// '=DEFINED_NAME_1,DEFINED_NAME_2',
130+
// ],
131+
];
132+
}
133+
}

0 commit comments

Comments
 (0)