From f2e24ecdd49c57ba8f7ae382099eeaf6225bd56e Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Wed, 20 Nov 2013 18:55:05 +0900 Subject: [PATCH] Comparison operators on strings are usually case insensitive Excel, Gnumeric and Google Spreadsheet are case insensitive, so the default behavior of PHPExcel is modified accordingly. However OpenOffice is case sensitive and is also supported via the compatibility mode of PHPExcel. Fixes #31 --- Classes/PHPExcel/Calculation.php | 53 +++++++++++++++++-- .../Classes/PHPExcel/CalculationTest.php | 35 ++++++++++++ .../CalculationBinaryComparisonOperation.data | 53 +++++++++++++++++++ 3 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 unitTests/Classes/PHPExcel/CalculationTest.php create mode 100644 unitTests/rawTestData/CalculationBinaryComparisonOperation.data diff --git a/Classes/PHPExcel/Calculation.php b/Classes/PHPExcel/Calculation.php index 152c98e1..19e8edd9 100644 --- a/Classes/PHPExcel/Calculation.php +++ b/Classes/PHPExcel/Calculation.php @@ -3557,15 +3557,37 @@ class PHPExcel_Calculation { if (is_string($operand1) && $operand1 > '' && $operand1{0} == '"') { $operand1 = self::_unwrapResult($operand1); } if (is_string($operand2) && $operand2 > '' && $operand2{0} == '"') { $operand2 = self::_unwrapResult($operand2); } + // Use case insensitive comparaison if not OpenOffice mode + if (PHPExcel_Calculation_Functions::getCompatibilityMode() != PHPExcel_Calculation_Functions::COMPATIBILITY_OPENOFFICE) + { + if (is_string($operand1)) { + $operand1 = strtoupper($operand1); + } + + if (is_string($operand2)) { + $operand2 = strtoupper($operand2); + } + } + + $useLowercaseFirstComparison = is_string($operand1) && is_string($operand2) && PHPExcel_Calculation_Functions::getCompatibilityMode() == PHPExcel_Calculation_Functions::COMPATIBILITY_OPENOFFICE; + // execute the necessary operation switch ($operation) { // Greater than case '>': - $result = ($operand1 > $operand2); + if ($useLowercaseFirstComparison) { + $result = $this->strcmpLowercaseFirst($operand1, $operand2) > 0; + } else { + $result = ($operand1 > $operand2); + } break; // Less than case '<': - $result = ($operand1 < $operand2); + if ($useLowercaseFirstComparison) { + $result = $this->strcmpLowercaseFirst($operand1, $operand2) < 0; + } else { + $result = ($operand1 < $operand2); + } break; // Equality case '=': @@ -3573,11 +3595,19 @@ class PHPExcel_Calculation { break; // Greater than or equal case '>=': - $result = ($operand1 >= $operand2); + if ($useLowercaseFirstComparison) { + $result = $this->strcmpLowercaseFirst($operand1, $operand2) >= 0; + } else { + $result = ($operand1 >= $operand2); + } break; // Less than or equal case '<=': - $result = ($operand1 <= $operand2); + if ($useLowercaseFirstComparison) { + $result = $this->strcmpLowercaseFirst($operand1, $operand2) <= 0; + } else { + $result = ($operand1 <= $operand2); + } break; // Inequality case '<>': @@ -3592,6 +3622,21 @@ class PHPExcel_Calculation { return TRUE; } // function _executeBinaryComparisonOperation() + /** + * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters + * @param string $str1 + * @param string $str2 + * @return integer + */ + private function strcmpLowercaseFirst($str1, $str2) + { + $from = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + $to = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $inversedStr1 = strtr($str1, $from, $to); + $inversedStr2 = strtr($str2, $from, $to); + + return strcmp($inversedStr1, $inversedStr2); + } private function _executeNumericBinaryOperation($cellID,$operand1,$operand2,$operation,$matrixFunction,&$stack) { // Validate the two operands diff --git a/unitTests/Classes/PHPExcel/CalculationTest.php b/unitTests/Classes/PHPExcel/CalculationTest.php new file mode 100644 index 00000000..8f8ce7e0 --- /dev/null +++ b/unitTests/Classes/PHPExcel/CalculationTest.php @@ -0,0 +1,35 @@ +_calculateFormulaValue($formula); + $this->assertEquals($expectedResultExcel, $resultExcel, 'should be Excel compatible'); + + PHPExcel_Calculation_Functions::setCompatibilityMode(PHPExcel_Calculation_Functions::COMPATIBILITY_OPENOFFICE); + $resultOpenOffice = \PHPExcel_Calculation::getInstance()->_calculateFormulaValue($formula); + $this->assertEquals($expectedResultOpenOffice, $resultOpenOffice, 'should be OpenOffice compatible'); + } + + public function providerBinaryComparisonOperation() + { + return new testDataFileIterator('rawTestData/CalculationBinaryComparisonOperation.data'); + } + +} diff --git a/unitTests/rawTestData/CalculationBinaryComparisonOperation.data b/unitTests/rawTestData/CalculationBinaryComparisonOperation.data new file mode 100644 index 00000000..de04cbf7 --- /dev/null +++ b/unitTests/rawTestData/CalculationBinaryComparisonOperation.data @@ -0,0 +1,53 @@ +# formula, expectedResultExcel, expectedResultOpenOffice +'=TRUE', TRUE, TRUE +'=1 + 2.5', 3.5, 3.5 +'=2.5 + 1', 3.5, 3.5 +'=1 - 2.5', -1.5, -1.5 +'=2.5 - 1', 1.5, 1.5 +'=3 > 1', TRUE, TRUE +'=3 > 3', FALSE, FALSE +'=1 > 3', FALSE, FALSE +'=3 < 1', FALSE, FALSE +'=3 < 3', FALSE, FALSE +'=1 < 3', TRUE, TRUE +'=3 = 1', FALSE, FALSE +'=3 = 3', TRUE, TRUE +'=1 = 1.0', TRUE, TRUE +'=3 >= 1', TRUE, TRUE +'=3 >= 3', TRUE, TRUE +'=1 >= 3', FALSE, FALSE +'=3 <= 1', FALSE, FALSE +'=3 <= 3', TRUE, TRUE +'=1 <= 3', TRUE, TRUE +'=3 <> 1', TRUE, TRUE +'=3 <> 3', FALSE, FALSE +'=1 <> 1.0', FALSE, FALSE +'="a" > "a"', FALSE, FALSE +'="A" > "A"', FALSE, FALSE +'="A" > "a"', FALSE, TRUE +'="a" > "A"', FALSE, FALSE +'="a" < "a"', FALSE, FALSE +'="A" < "A"', FALSE, FALSE +'="A" < "a"', FALSE, FALSE +'="a" < "A"', FALSE, TRUE +'="a" = "a"', TRUE, TRUE +'="A" = "A"', TRUE, TRUE +'="A" = "a"', TRUE, FALSE +'="a" = "A"', TRUE, FALSE +'="a" <= "a"', TRUE, TRUE +'="A" <= "A"', TRUE, TRUE +'="A" <= "a"', TRUE, FALSE +'="a" <= "A"', TRUE, TRUE +'="a" >= "a"', TRUE, TRUE +'="A" >= "A"', TRUE, TRUE +'="A" >= "a"', TRUE, TRUE +'="a" >= "A"', TRUE, FALSE +'="a" <> "a"', FALSE, FALSE +'="A" <> "A"', FALSE, FALSE +'="A" <> "a"', FALSE, TRUE +'="a" <> "A"', FALSE, TRUE +'="A" > "b"', FALSE, TRUE +'="a" > "b"', FALSE, FALSE +'="b" > "a"', TRUE, TRUE +'="b" > "A"', TRUE, FALSE +'="a2" > "a10"', TRUE, TRUE // Test natural sorting is not used