See Release Notes
Long Term Support Release
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Calculation; 4 5 use PhpOffice\PhpSpreadsheet\Shared\StringHelper; 6 7 class BinaryComparison 8 { 9 /** 10 * Epsilon Precision used for comparisons in calculations. 11 */ 12 private const DELTA = 0.1e-12; 13 14 /** 15 * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters. 16 * 17 * @param null|string $str1 First string value for the comparison 18 * @param null|string $str2 Second string value for the comparison 19 */ 20 private static function strcmpLowercaseFirst($str1, $str2): int 21 { 22 $inversedStr1 = StringHelper::strCaseReverse($str1 ?? ''); 23 $inversedStr2 = StringHelper::strCaseReverse($str2 ?? ''); 24 25 return strcmp($inversedStr1, $inversedStr2); 26 } 27 28 /** 29 * PHP8.1 deprecates passing null to strcmp. 30 * 31 * @param null|string $str1 First string value for the comparison 32 * @param null|string $str2 Second string value for the comparison 33 */ 34 private static function strcmpAllowNull($str1, $str2): int 35 { 36 return strcmp($str1 ?? '', $str2 ?? ''); 37 } 38 39 /** 40 * @param mixed $operand1 41 * @param mixed $operand2 42 */ 43 public static function compare($operand1, $operand2, string $operator): bool 44 { 45 // Simple validate the two operands if they are string values 46 if (is_string($operand1) && $operand1 > '' && $operand1[0] == Calculation::FORMULA_STRING_QUOTE) { 47 $operand1 = Calculation::unwrapResult($operand1); 48 } 49 if (is_string($operand2) && $operand2 > '' && $operand2[0] == Calculation::FORMULA_STRING_QUOTE) { 50 $operand2 = Calculation::unwrapResult($operand2); 51 } 52 53 // Use case insensitive comparaison if not OpenOffice mode 54 if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) { 55 if (is_string($operand1)) { 56 $operand1 = StringHelper::strToUpper($operand1); 57 } 58 if (is_string($operand2)) { 59 $operand2 = StringHelper::strToUpper($operand2); 60 } 61 } 62 63 $useLowercaseFirstComparison = is_string($operand1) && 64 is_string($operand2) && 65 Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE; 66 67 return self::evaluateComparison($operand1, $operand2, $operator, $useLowercaseFirstComparison); 68 } 69 70 /** 71 * @param mixed $operand1 72 * @param mixed $operand2 73 */ 74 private static function evaluateComparison($operand1, $operand2, string $operator, bool $useLowercaseFirstComparison): bool 75 { 76 switch ($operator) { 77 // Equality 78 case '=': 79 return self::equal($operand1, $operand2); 80 // Greater than 81 case '>': 82 return self::greaterThan($operand1, $operand2, $useLowercaseFirstComparison); 83 // Less than 84 case '<': 85 return self::lessThan($operand1, $operand2, $useLowercaseFirstComparison); 86 // Greater than or equal 87 case '>=': 88 return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison); 89 // Less than or equal 90 case '<=': 91 return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison); 92 // Inequality 93 case '<>': 94 return self::notEqual($operand1, $operand2); 95 default: 96 throw new Exception('Unsupported binary comparison operator'); 97 } 98 } 99 100 /** 101 * @param mixed $operand1 102 * @param mixed $operand2 103 */ 104 private static function equal($operand1, $operand2): bool 105 { 106 if (is_numeric($operand1) && is_numeric($operand2)) { 107 $result = (abs($operand1 - $operand2) < self::DELTA); 108 } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) { 109 $result = $operand1 == $operand2; 110 } else { 111 $result = self::strcmpAllowNull($operand1, $operand2) == 0; 112 } 113 114 return $result; 115 } 116 117 /** 118 * @param mixed $operand1 119 * @param mixed $operand2 120 */ 121 private static function greaterThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool 122 { 123 if (is_numeric($operand1) && is_numeric($operand2)) { 124 $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 > $operand2)); 125 } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) { 126 $result = $operand1 >= $operand2; 127 } elseif ($useLowercaseFirstComparison) { 128 $result = self::strcmpLowercaseFirst($operand1, $operand2) >= 0; 129 } else { 130 $result = self::strcmpAllowNull($operand1, $operand2) >= 0; 131 } 132 133 return $result; 134 } 135 136 /** 137 * @param mixed $operand1 138 * @param mixed $operand2 139 */ 140 private static function lessThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool 141 { 142 if (is_numeric($operand1) && is_numeric($operand2)) { 143 $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 < $operand2)); 144 } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) { 145 $result = $operand1 <= $operand2; 146 } elseif ($useLowercaseFirstComparison) { 147 $result = self::strcmpLowercaseFirst($operand1, $operand2) <= 0; 148 } else { 149 $result = self::strcmpAllowNull($operand1, $operand2) <= 0; 150 } 151 152 return $result; 153 } 154 155 /** 156 * @param mixed $operand1 157 * @param mixed $operand2 158 */ 159 private static function greaterThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool 160 { 161 return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true; 162 } 163 164 /** 165 * @param mixed $operand1 166 * @param mixed $operand2 167 */ 168 private static function lessThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool 169 { 170 return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true; 171 } 172 173 /** 174 * @param mixed $operand1 175 * @param mixed $operand2 176 */ 177 private static function notEqual($operand1, $operand2): bool 178 { 179 return self::equal($operand1, $operand2) !== true; 180 } 181 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body