Basic unit tests for formula parsing, in preparation for work on fully supporting the Union Operator, and for providing support for Structured References
This commit is contained in:
parent
709e2ae5ca
commit
483ef53855
|
|
@ -108,7 +108,8 @@ class Calculation
|
||||||
'+' => true, '-' => true, '*' => true, '/' => true,
|
'+' => true, '-' => true, '*' => true, '/' => true,
|
||||||
'^' => true, '&' => true, '%' => false, '~' => false,
|
'^' => true, '&' => true, '%' => false, '~' => false,
|
||||||
'>' => true, '<' => true, '=' => true, '>=' => true,
|
'>' => true, '<' => true, '=' => true, '>=' => true,
|
||||||
'<=' => true, '<>' => true, '|' => true, ':' => true,
|
'<=' => true, '<>' => true, '∩' => true, '∪' => true,
|
||||||
|
':' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -120,7 +121,7 @@ class Calculation
|
||||||
'+' => true, '-' => true, '*' => true, '/' => true,
|
'+' => true, '-' => true, '*' => true, '/' => true,
|
||||||
'^' => true, '&' => true, '>' => true, '<' => true,
|
'^' => true, '&' => true, '>' => true, '<' => true,
|
||||||
'=' => true, '>=' => true, '<=' => true, '<>' => true,
|
'=' => true, '>=' => true, '<=' => true, '<>' => true,
|
||||||
'|' => true, ':' => true,
|
'∩' => true, '∪' => true, ':' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -3872,7 +3873,7 @@ class Calculation
|
||||||
'*' => 0, '/' => 0, // Multiplication and Division
|
'*' => 0, '/' => 0, // Multiplication and Division
|
||||||
'+' => 0, '-' => 0, // Addition and Subtraction
|
'+' => 0, '-' => 0, // Addition and Subtraction
|
||||||
'&' => 0, // Concatenation
|
'&' => 0, // Concatenation
|
||||||
'|' => 0, ':' => 0, // Intersect and Range
|
'∪' => 0, '∩' => 0, ':' => 0, // Union, Intersect and Range
|
||||||
'>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison
|
'>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -3884,8 +3885,9 @@ class Calculation
|
||||||
// This list includes all valid operators, whether binary (including boolean) or unary (such as %)
|
// This list includes all valid operators, whether binary (including boolean) or unary (such as %)
|
||||||
// Array key is the operator, the value is its precedence
|
// Array key is the operator, the value is its precedence
|
||||||
private static $operatorPrecedence = [
|
private static $operatorPrecedence = [
|
||||||
':' => 8, // Range
|
':' => 9, // Range
|
||||||
'|' => 7, // Intersect
|
'∩' => 8, // Intersect
|
||||||
|
'∪' => 7, // Union
|
||||||
'~' => 6, // Negation
|
'~' => 6, // Negation
|
||||||
'%' => 5, // Percentage
|
'%' => 5, // Percentage
|
||||||
'^' => 4, // Exponentiation
|
'^' => 4, // Exponentiation
|
||||||
|
|
@ -3957,7 +3959,7 @@ class Calculation
|
||||||
++$index;
|
++$index;
|
||||||
} elseif ($opCharacter == '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded?
|
} elseif ($opCharacter == '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded?
|
||||||
++$index; // Drop the redundant plus symbol
|
++$index; // Drop the redundant plus symbol
|
||||||
} elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde or pipe, because they are legal
|
} elseif ((($opCharacter == '~') || ($opCharacter == '∩') || ($opCharacter == '∪')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde, union or intersect because they are legal
|
||||||
return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression
|
return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression
|
||||||
} elseif ((isset(self::$operators[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack?
|
} elseif ((isset(self::$operators[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack?
|
||||||
while (
|
while (
|
||||||
|
|
@ -4338,7 +4340,7 @@ class Calculation
|
||||||
) {
|
) {
|
||||||
$output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
|
$output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
|
||||||
}
|
}
|
||||||
$stack->push('Binary Operator', '|'); // Put an Intersect Operator on the stack
|
$stack->push('Binary Operator', '∩'); // Put an Intersect Operator on the stack
|
||||||
$expectingOperator = false;
|
$expectingOperator = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4638,7 +4640,7 @@ class Calculation
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case '|': // Intersect
|
case '∩': // Intersect
|
||||||
$rowIntersect = array_intersect_key($operand1, $operand2);
|
$rowIntersect = array_intersect_key($operand1, $operand2);
|
||||||
$cellIntersect = $oCol = $oRow = [];
|
$cellIntersect = $oCol = $oRow = [];
|
||||||
foreach (array_keys($rowIntersect) as $row) {
|
foreach (array_keys($rowIntersect) as $row) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheetTests\Calculation;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class ParseFormulaTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider providerBinaryOperations
|
||||||
|
*/
|
||||||
|
public function testParseOperations(array $expectedStack, string $formula): void
|
||||||
|
{
|
||||||
|
$parser = Calculation::getInstance();
|
||||||
|
$stack = $parser->parseFormula($formula);
|
||||||
|
self::assertSame($expectedStack, $stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function providerBinaryOperations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Unary negative with Value' => [
|
||||||
|
[
|
||||||
|
['type' => 'Value', 'value' => 3, 'reference' => null],
|
||||||
|
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||||
|
],
|
||||||
|
'=-3',
|
||||||
|
],
|
||||||
|
'Unary negative percentage with Value' => [
|
||||||
|
[
|
||||||
|
['type' => 'Value', 'value' => 3, 'reference' => null],
|
||||||
|
['type' => 'Unary Operator', 'value' => '%', 'reference' => null],
|
||||||
|
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||||
|
],
|
||||||
|
'=-3%',
|
||||||
|
],
|
||||||
|
'Binary minus with Values' => [
|
||||||
|
[
|
||||||
|
['type' => 'Value', 'value' => 3, 'reference' => null],
|
||||||
|
['type' => 'Value', 'value' => 4, 'reference' => null],
|
||||||
|
['type' => 'Binary Operator', 'value' => '-', 'reference' => null],
|
||||||
|
],
|
||||||
|
'=3-4',
|
||||||
|
],
|
||||||
|
'Unary negative with Cell Reference' => [
|
||||||
|
[
|
||||||
|
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
|
||||||
|
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||||
|
],
|
||||||
|
'=-A1',
|
||||||
|
],
|
||||||
|
'Unary negative with FQ Cell Reference' => [
|
||||||
|
[
|
||||||
|
['type' => 'Cell Reference', 'value' => "'Sheet 1'!A1", 'reference' => "'Sheet 1'!A1"],
|
||||||
|
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||||
|
],
|
||||||
|
"=-'Sheet 1'!A1",
|
||||||
|
],
|
||||||
|
'Unary negative percentage with Cell Reference' => [
|
||||||
|
[
|
||||||
|
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
|
||||||
|
['type' => 'Unary Operator', 'value' => '%', 'reference' => null],
|
||||||
|
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||||
|
],
|
||||||
|
'=-A1%',
|
||||||
|
],
|
||||||
|
'Unary negative with Defined Name' => [
|
||||||
|
[
|
||||||
|
['type' => 'Defined Name', 'value' => 'DEFINED_NAME', 'reference' => 'DEFINED_NAME'],
|
||||||
|
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||||
|
],
|
||||||
|
'=-DEFINED_NAME',
|
||||||
|
],
|
||||||
|
'Unary negative percentage with Defined Name' => [
|
||||||
|
[
|
||||||
|
['type' => 'Defined Name', 'value' => 'DEFINED_NAME', 'reference' => 'DEFINED_NAME'],
|
||||||
|
['type' => 'Unary Operator', 'value' => '%', 'reference' => null],
|
||||||
|
['type' => 'Unary Operator', 'value' => '~', 'reference' => null],
|
||||||
|
],
|
||||||
|
'=-DEFINED_NAME%',
|
||||||
|
],
|
||||||
|
'Cell Range' => [
|
||||||
|
[
|
||||||
|
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
|
||||||
|
['type' => 'Cell Reference', 'value' => 'C3', 'reference' => 'C3'],
|
||||||
|
['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
|
||||||
|
],
|
||||||
|
'=A1:C3',
|
||||||
|
],
|
||||||
|
'Cell Range Intersection' => [
|
||||||
|
[
|
||||||
|
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
|
||||||
|
['type' => 'Cell Reference', 'value' => 'C3', 'reference' => 'C3'],
|
||||||
|
['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
|
||||||
|
['type' => 'Cell Reference', 'value' => 'B2', 'reference' => 'B2'],
|
||||||
|
['type' => 'Cell Reference', 'value' => 'D4', 'reference' => 'D4'],
|
||||||
|
['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
|
||||||
|
['type' => 'Binary Operator', 'value' => '∩', 'reference' => null],
|
||||||
|
],
|
||||||
|
'=A1:C3 B2:D4',
|
||||||
|
],
|
||||||
|
'Named Range Intersection' => [
|
||||||
|
[
|
||||||
|
['type' => 'Defined Name', 'value' => 'DEFINED_NAME_1', 'reference' => 'DEFINED_NAME_1'],
|
||||||
|
['type' => 'Defined Name', 'value' => 'DEFINED_NAME_2', 'reference' => 'DEFINED_NAME_2'],
|
||||||
|
['type' => 'Binary Operator', 'value' => '∩', 'reference' => null],
|
||||||
|
],
|
||||||
|
'=DEFINED_NAME_1 DEFINED_NAME_2',
|
||||||
|
],
|
||||||
|
// 'Cell Range Union' => [
|
||||||
|
// [
|
||||||
|
// ['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
|
||||||
|
// ['type' => 'Cell Reference', 'value' => 'C3', 'reference' => 'C3'],
|
||||||
|
// ['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
|
||||||
|
// ['type' => 'Cell Reference', 'value' => 'B2', 'reference' => 'B2'],
|
||||||
|
// ['type' => 'Cell Reference', 'value' => 'D4', 'reference' => 'D4'],
|
||||||
|
// ['type' => 'Binary Operator', 'value' => ':', 'reference' => null],
|
||||||
|
// ['type' => 'Binary Operator', 'value' => '∪', 'reference' => null],
|
||||||
|
// ],
|
||||||
|
// '=A1:C3,B2:D4',
|
||||||
|
// ],
|
||||||
|
// 'Named Range Union' => [
|
||||||
|
// [
|
||||||
|
// ['type' => 'Defined Name', 'value' => 'DEFINED_NAME_1', 'reference' => 'DEFINED_NAME_1'],
|
||||||
|
// ['type' => 'Defined Name', 'value' => 'DEFINED_NAME_2', 'reference' => 'DEFINED_NAME_2'],
|
||||||
|
// ['type' => 'Binary Operator', 'value' => '∪', 'reference' => null],
|
||||||
|
// ],
|
||||||
|
// '=DEFINED_NAME_1,DEFINED_NAME_2',
|
||||||
|
// ],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue