diff --git a/samples/Basic/40_Duplicate_style.php b/samples/Basic/40_Duplicate_style.php index 0366703d90..38f7fb495e 100644 --- a/samples/Basic/40_Duplicate_style.php +++ b/samples/Basic/40_Duplicate_style.php @@ -30,7 +30,7 @@ } } $d = microtime(true) - $t; -$helper->log('Add data (end) . time: ' . round((string) ($d . 2)) . ' s'); +$helper->log('Add data (end) . time: ' . (string) round($d, 2) . ' s'); // Save $helper->write($spreadsheet, __FILE__); diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index e114a00d19..a3f45f19e4 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -2314,12 +2314,12 @@ class Calculation ], 'SUMIF' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMIF'], + 'functionCall' => [Statistical\Conditional::class, 'SUMIF'], 'argumentCount' => '2,3', ], 'SUMIFS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMIFS'], + 'functionCall' => [Statistical\Conditional::class, 'SUMIFS'], 'argumentCount' => '3+', ], 'SUMPRODUCT' => [ diff --git a/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php b/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php index 2148ebc0b9..cf48bd8815 100644 --- a/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php +++ b/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php @@ -162,6 +162,10 @@ private static function processCondition(string $criterion, array $fields, array $dataValue = ($dataValues[$key]) ? 'TRUE' : 'FALSE'; } elseif ($dataValues[$key] !== null) { $dataValue = $dataValues[$key]; + // escape quotes if we have a string containing quotes + if (is_string($dataValue) && strpos($dataValue, '"') !== false) { + $dataValue = str_replace('"', '""', $dataValue); + } $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue; } diff --git a/src/PhpSpreadsheet/Calculation/MathTrig.php b/src/PhpSpreadsheet/Calculation/MathTrig.php index f12cc9df5a..5a966cbfed 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig.php @@ -1284,44 +1284,22 @@ public static function SUM(...$args) * Totals the values of cells that contain numbers within the list of arguments * * Excel Function: - * SUMIF(value1[,value2[, ...]],condition) + * SUMIF(range, criteria, [sum_range]) * - * @param mixed $aArgs Data values - * @param string $condition the criteria that defines which cells will be summed - * @param mixed $sumArgs + * @Deprecated 1.17.0 + * + * @see Statistical\Conditional::SUMIF() + * Use the SUMIF() method in the Statistical\Conditional class instead + * + * @param mixed $range Data values + * @param string $criteria the criteria that defines which cells will be summed + * @param mixed $sumRange * * @return float */ - public static function SUMIF($aArgs, $condition, $sumArgs = []) + public static function SUMIF($range, $criteria, $sumRange = []) { - $returnValue = 0; - - $aArgs = Functions::flattenArray($aArgs); - $sumArgs = Functions::flattenArray($sumArgs); - if (empty($sumArgs)) { - $sumArgs = $aArgs; - } - $condition = Functions::ifCondition($condition); - // Loop through arguments - foreach ($aArgs as $key => $arg) { - if (!is_numeric($arg)) { - $arg = str_replace('"', '""', $arg); - $arg = Calculation::wrapResult(strtoupper($arg)); - } - - $testCondition = '=' . $arg . $condition; - $sumValue = array_key_exists($key, $sumArgs) ? $sumArgs[$key] : 0; - - if ( - is_numeric($sumValue) && - Calculation::getInstance()->_calculateFormulaValue($testCondition) - ) { - // Is it a value within our criteria and only numeric can be added to the result - $returnValue += $sumValue; - } - } - - return $returnValue; + return Statistical\Conditional::SUMIF($range, $criteria, $sumRange); } /** @@ -1330,7 +1308,12 @@ public static function SUMIF($aArgs, $condition, $sumArgs = []) * Totals the values of cells that contain numbers within the list of arguments * * Excel Function: - * SUMIFS(value1[,value2[, ...]],condition) + * SUMIFS(sum_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) + * + * @Deprecated 1.17.0 + * + * @see Statistical\Conditional::SUMIFS() + * Use the SUMIFS() method in the Statistical\Conditional class instead * * @param mixed $args Data values * @@ -1338,47 +1321,7 @@ public static function SUMIF($aArgs, $condition, $sumArgs = []) */ public static function SUMIFS(...$args) { - $arrayList = $args; - - // Return value - $returnValue = 0; - - $sumArgs = Functions::flattenArray(array_shift($arrayList)); - $aArgsArray = []; - $conditions = []; - - while (count($arrayList) > 0) { - $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); - $conditions[] = Functions::ifCondition(array_shift($arrayList)); - } - - // Loop through each sum and see if arguments and conditions are true - foreach ($sumArgs as $index => $value) { - $valid = true; - - foreach ($conditions as $cidx => $condition) { - $arg = $aArgsArray[$cidx][$index]; - - // Loop through arguments - if (!is_numeric($arg)) { - $arg = Calculation::wrapResult(strtoupper($arg)); - } - $testCondition = '=' . $arg . $condition; - if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is not a value within our criteria - $valid = false; - - break; // if false found, don't need to check other conditions - } - } - - if ($valid) { - $returnValue += $value; - } - } - - // Return - return $returnValue; + return Statistical\Conditional::SUMIFS(...$args); } /** diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php index ae33401188..7ed1e71485 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php @@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Database\DCount; use PhpOffice\PhpSpreadsheet\Calculation\Database\DMax; use PhpOffice\PhpSpreadsheet\Calculation\Database\DMin; +use PhpOffice\PhpSpreadsheet\Calculation\Database\DSum; use PhpOffice\PhpSpreadsheet\Calculation\Functions; class Conditional @@ -30,18 +31,7 @@ class Conditional */ public static function AVERAGEIF($range, $condition, $averageRange = []) { - $range = Functions::flattenArray($range); - $averageRange = Functions::flattenArray($averageRange); - if (empty($averageRange)) { - $averageRange = $range; - } - - $database = array_map( - null, - array_merge([self::CONDITION_COLUMN_NAME], $range), - array_merge([self::VALUE_COLUMN_NAME], $averageRange) - ); - + $database = self::databaseFromRangeAndValue($range, $averageRange); $condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]]; return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $condition); @@ -64,11 +54,11 @@ public static function AVERAGEIFS(...$args) if (empty($args)) { return 0.0; } elseif (count($args) === 3) { - return self::AVERAGEIF($args[2], $args[1], $args[0]); + return self::AVERAGEIF($args[1], $args[2], $args[0]); } - $conditions = self::buildConditionSetForRange(...$args); - $database = self::buildDatabaseWithRange(...$args); + $conditions = self::buildConditionSetForValueRange(...$args); + $database = self::buildDatabaseWithValueRange(...$args); return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); } @@ -146,8 +136,8 @@ public static function MAXIFS(...$args) return 0.0; } - $conditions = self::buildConditionSetForRange(...$args); - $database = self::buildDatabaseWithRange(...$args); + $conditions = self::buildConditionSetForValueRange(...$args); + $database = self::buildDatabaseWithValueRange(...$args); return DMax::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); } @@ -170,12 +160,60 @@ public static function MINIFS(...$args) return 0.0; } - $conditions = self::buildConditionSetForRange(...$args); - $database = self::buildDatabaseWithRange(...$args); + $conditions = self::buildConditionSetForValueRange(...$args); + $database = self::buildDatabaseWithValueRange(...$args); return DMin::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); } + /** + * SUMIF. + * + * Totals the values of cells that contain numbers within the list of arguments + * + * Excel Function: + * SUMIF(range, criteria, [sum_range]) + * + * @param mixed $range Data values + * @param mixed $sumRange + * @param mixed $condition + * + * @return float + */ + public static function SUMIF($range, $condition, $sumRange = []) + { + $database = self::databaseFromRangeAndValue($range, $sumRange); + $condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]]; + + return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $condition); + } + + /** + * SUMIFS. + * + * Counts the number of cells that contain numbers within the list of arguments + * + * Excel Function: + * SUMIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2]…) + * + * @param mixed $args Pairs of Ranges and Criteria + * + * @return null|float|string + */ + public static function SUMIFS(...$args) + { + if (empty($args)) { + return 0.0; + } elseif (count($args) === 3) { + return self::SUMIF($args[1], $args[2], $args[0]); + } + + $conditions = self::buildConditionSetForValueRange(...$args); + $database = self::buildDatabaseWithValueRange(...$args); + + return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $conditions); + } + private static function buildConditionSet(...$args): array { $conditions = self::buildConditions(1, ...$args); @@ -183,7 +221,7 @@ private static function buildConditionSet(...$args): array return array_map(null, ...$conditions); } - private static function buildConditionSetForRange(...$args): array + private static function buildConditionSetForValueRange(...$args): array { $conditions = self::buildConditions(2, ...$args); @@ -220,7 +258,7 @@ private static function buildDatabase(...$args): array return self::buildDataSet(0, $database, ...$args); } - private static function buildDatabaseWithRange(...$args): array + private static function buildDatabaseWithValueRange(...$args): array { $database = []; $database[] = array_merge( @@ -245,4 +283,22 @@ private static function buildDataSet(int $startOffset, array $database, ...$args return array_map(null, ...$database); } + + private static function databaseFromRangeAndValue(array $range, array $valueRange = []): array + { + $range = Functions::flattenArray($range); + + $valueRange = Functions::flattenArray($valueRange); + if (empty($valueRange)) { + $valueRange = $range; + } + + $database = array_map( + null, + array_merge([self::CONDITION_COLUMN_NAME], $range), + array_merge([self::VALUE_COLUMN_NAME], $valueRange) + ); + + return $database; + } } diff --git a/tests/data/Calculation/MathTrig/SUMIF.php b/tests/data/Calculation/MathTrig/SUMIF.php index 4bd7fc4d60..9cba4d15f0 100644 --- a/tests/data/Calculation/MathTrig/SUMIF.php +++ b/tests/data/Calculation/MathTrig/SUMIF.php @@ -120,4 +120,22 @@ [5], ], ], + [ + 157559, + ['Jan', 'Jan', 'Jan', 'Jan', 'Feb', 'Feb', 'Feb', 'Feb'], + 'Feb', + [36693, 22100, 53321, 34440, 29889, 50090, 32080, 45500], + ], + [ + 66582, + ['North 1', 'North 2', 'South 1', 'South 2', 'North 1', 'North 2', 'South 1', 'South 2,'], + 'North 1', + [36693, 22100, 53321, 34440, 29889, 50090, 32080, 45500], + ], + [ + 138772, + ['North 1', 'North 2', 'South 1', 'South 2', 'North 1', 'North 2', 'South 1', 'South 2,'], + 'North ?', + [36693, 22100, 53321, 34440, 29889, 50090, 32080, 45500], + ], ]; diff --git a/tests/data/Calculation/MathTrig/SUMIFS.php b/tests/data/Calculation/MathTrig/SUMIFS.php index a374dd1494..9d860c30aa 100644 --- a/tests/data/Calculation/MathTrig/SUMIFS.php +++ b/tests/data/Calculation/MathTrig/SUMIFS.php @@ -1,6 +1,9 @@ 2', + ['Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol', 'Jeff', 'Chris', 'Carol'], + 'Jeff', + ], ]; diff --git a/tests/data/Calculation/Statistical/AVERAGEIFS.php b/tests/data/Calculation/Statistical/AVERAGEIFS.php index 6c94300cc1..d5bc6dadb9 100644 --- a/tests/data/Calculation/Statistical/AVERAGEIFS.php +++ b/tests/data/Calculation/Statistical/AVERAGEIFS.php @@ -1,6 +1,9 @@