Просмотр исходного кода

Cleanup, minor spreadsheet bugs

main
Rocketsoup 1 год назад
Родитель
Сommit
75b163050f
4 измененных файлов: 179 добавлений и 170 удалений
  1. 3
    3
      js/spreadsheet.js
  2. 1
    1
      js/spreadsheet.min.js
  3. 169
    161
      php/spreadsheet.php
  4. 6
    5
      playground.html

+ 3
- 3
js/spreadsheet.js Просмотреть файл

@@ -578,7 +578,7 @@ class CellExpressionSet {
578 578
 		if (args.length == 0) {
579 579
 			throw new CellEvaluationException("AND requires one or more arguments");
580 580
 		}
581
-		const values = args.map((arg) => this.#evaluate(arg, address));
581
+		const values = this.#flattenedNumericArguments('AND', args, address, false);
582 582
 		for (const value of values) {
583 583
 			const result = value.booleanValue();
584 584
 			if (result === null) {
@@ -842,7 +842,7 @@ class CellExpressionSet {
842 842
 		if (args.length == 0) {
843 843
 			throw new CellEvaluationException("OR requires one or more arguments");
844 844
 		}
845
-		const values = args.map((arg) => this.#evaluate(arg, address));
845
+		const values = this.#flattenedNumericArguments('OR', args, address, false);
846 846
 		for (const value of values) {
847 847
 			const result = value.booleanValue();
848 848
 			if (result === null) {
@@ -974,7 +974,7 @@ class CellExpressionSet {
974 974
 		if (args.length == 0) {
975 975
 			throw new CellEvaluationException("XOR requires one or more arguments");
976 976
 		}
977
-		const values = args.map((arg) => this.#evaluate(arg, address));
977
+		const values = this.#flattenedNumericArguments('XOR', args, address, false);
978 978
 		var result = null;
979 979
 		for (const value of values) {
980 980
 			const b = value.booleanValue();

+ 1
- 1
js/spreadsheet.min.js
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 169
- 161
php/spreadsheet.php Просмотреть файл

@@ -6,8 +6,6 @@ class CellException extends Error {
6 6
 	/**
7 7
 	 * Brief symbol to show in the cell to signify an error occurred during
8 8
 	 * evaluation.
9
-	 *
10
-	 * @$string
11 9
 	 */
12 10
 	public string $errorSymbol;
13 11
 
@@ -151,16 +149,17 @@ class CellExpressionSet {
151 149
 		$colCount = $this->grid->columnCount;
152 150
 
153 151
 		// Make queue of cell addresses with expressions in them
154
-		/** @$CellAddress[] */
152
+		/** @var CellAddress[] */
155 153
 		$expressionAddressQueue = [];
156
-		$range = new CellAddressRange(new CellAddress(0, 0), new CellAddress($colCount - 1, $rowCount - 1));
154
+		$range = new CellAddressRange(new CellAddress(0, 0),
155
+			new CellAddress($colCount - 1, $rowCount - 1));
157 156
 		foreach ($range->cellsIn($this->grid) as $addressStr => $cell) {
158 157
 			$address = CellAddress::fromString($addressStr);
159 158
 			if ($address === null) {
160 159
 				error_log("Couldn't parse address string {$addressStr}!");
161 160
 			}
162 161
 			$value = $cell->originalValue;
163
-			if ($value->type != CellValue::TYPE_FORMULA) {
162
+			if ($value->type !== CellValue::TYPE_FORMULA) {
164 163
 				$cell->outputValue = $value;
165 164
 				$cell->isCalculated = false;
166 165
 				continue;
@@ -187,16 +186,16 @@ class CellExpressionSet {
187 186
 	}
188 187
 
189 188
 	/**
190
-	 * Attempts to evaluate expressions at the given `addresses`. If an
189
+	 * Attempts to evaluate expressions at the given `$addresses`. If an
191 190
 	 * expression has unevaluated references, the expression is moved to the
192 191
 	 * end of the queue and tried again later. When this method returns, any
193
-	 * elements left in `addresses` can be considered circular references.
192
+	 * elements left in `$addresses` can be considered circular references.
194 193
 	 * 
195 194
 	 * @param CellAddress[] $addresses  mutable queue of formula addresses
196 195
 	 */
197 196
 	private function processExpressionQueue(array &$addresses) {
198 197
 		$requeueCount = 0;
199
-		while (sizeof($addresses) > 0 && $requeueCount < sizeof($addresses)) {
198
+		while (count($addresses) > 0 && $requeueCount < count($addresses)) {
200 199
 			$address = $addresses[0];
201 200
 			array_splice($addresses, 0, 1);
202 201
 			$cell = $this->grid->cellAt($address);
@@ -207,15 +206,18 @@ class CellExpressionSet {
207 206
 					$cell->outputValue = $result;
208 207
 					$requeueCount = 0;
209 208
 				} elseif (is_array($result)) {
210
-					if (sizeof($result) == 1) {
209
+					if (count($result) === 1) {
211 210
 						$cell->outputValue = $result[0];
212 211
 						$requeueCount = 0;
213 212
 					} else {
214
-						throw new CellEvaluationException("Expression resolved to " . sizeof($result) . " values, single value expected");
213
+						throw new CellEvaluationException("Expression resolved to " .
214
+							count($result) . " values, single value expected");
215 215
 					}
216 216
 				} else {
217
-					$typename = gettype($result) == 'object' ? get_class($result) : gettype($result);
218
-					throw new CellEvaluationException("Expression resolved to {$typename}, expected CellValue");
217
+					$typename = gettype($result) === 'object' ? get_class($result)
218
+						: gettype($result);
219
+					throw new CellEvaluationException(
220
+						"Expression resolved to {$typename}, expected CellValue");
219 221
 				}
220 222
 			} catch (CellDependencyException $e) {
221 223
 				// Depends on a value that hasn't been calculated yet
@@ -230,7 +232,7 @@ class CellExpressionSet {
230 232
 
231 233
 	/**
232 234
 	 * Autofills a formula, transposing the formula to each affected cell and
233
-	 * stored in `parsedExpression`, and each address is queued in `addresses`
235
+	 * stored in `$parsedExpression`, and each address is queued in `$addresses`
234 236
 	 * for evaluation.
235 237
 	 *
236 238
 	 * @param CellExpression $expression  autofilled formula
@@ -270,7 +272,7 @@ class CellExpressionSet {
270 272
 	 * @param CellAddress $address  location of expression
271 273
 	 * @return CellValue|CellValue[]  results
272 274
 	 */
273
-	private function evaluate(CellExpression $expr, CellAddress $address) {
275
+	private function evaluate(CellExpression $expr, CellAddress $address): CellValue|array {
274 276
 		$result = $this->preevaluate($expr, $address);
275 277
 		if ($result instanceof CellValue) {
276 278
 			// Expression included formatting override. Apply it to value.
@@ -289,9 +291,9 @@ class CellExpressionSet {
289 291
 	 * @param CellExpression $expr  expression to evaluate
290 292
 	 * @param CellAddress $address  location of expression
291 293
 	 * @return CellValue|CellValue[]  results
292
-	 * @throws CellException if evaluation fails for any reason
294
+	 * @throws CellException  if evaluation fails for any reason
293 295
 	 */
294
-	private function preevaluate(CellExpression $expr, CellAddress $address) {
296
+	private function preevaluate(CellExpression $expr, CellAddress $address): CellValue|array {
295 297
 		switch ($expr->op) {
296 298
 			case CellExpressionOperation::Number:
297 299
 			case CellExpressionOperation::String:
@@ -404,7 +406,7 @@ class CellExpressionSet {
404 406
 	 * @param array $args  raw arguments
405 407
 	 * @param CellAddress $address  location of the expression
406 408
 	 * @return CellValue  result
407
-	 * @throws CellException if evaluation fails for any reason
409
+	 * @throws CellException  if evaluation fails for any reason
408 410
 	 */
409 411
 	private function callFunction(string $functionName, array $args, CellAddress $address): CellValue {
410 412
 		switch (mb_strtoupper($functionName)) {
@@ -446,17 +448,19 @@ class CellExpressionSet {
446 448
 	 * @param array $args  raw arguments
447 449
 	 * @param CellAddress $address  address of the formula
448 450
 	 * @return CellValue[]  numeric arguments
449
-	 * @throws CellSyntaxException if wrong number of arguments is passed
450
-	 * @throws CellEvaluationException if an argument does not resolve to a numeric value
451
+	 * @throws CellSyntaxException  if wrong number of arguments is passed
452
+	 * @throws CellEvaluationException  if an argument does not resolve to a numeric value
451 453
 	 */
452 454
 	private function assertNumericArguments(string $functionName, int $minArgs,
453 455
 			int $maxArgs, array $args, CellAddress $address): array {
454
-		$argCount = sizeof($args);
456
+		$argCount = count($args);
455 457
 		if ($argCount < $minArgs || $argCount > $maxArgs) {
456
-			if ($minArgs == $maxArgs) {
457
-				throw new CellSyntaxException("{$functionName}() expects {$minArgs} arguments, got {$argCount}");
458
+			if ($minArgs === $maxArgs) {
459
+				throw new CellSyntaxException("{$functionName}() expects {$minArgs} " +
460
+					"arguments, got {$argCount}");
458 461
 			}
459
-			throw new CellSyntaxException("{$functionName}() expects between {$minArgs} and {$maxArgs} arguments, got {$argCount}");
462
+			throw new CellSyntaxException("{$functionName}() expects between " +
463
+				"{$minArgs} and {$maxArgs} arguments, got {$argCount}");
460 464
 		}
461 465
 		$out = [];
462 466
 		foreach ($args as $argument) {
@@ -534,10 +538,10 @@ class CellExpressionSet {
534 538
 	 * @return CellValue  result
535 539
 	 */
536 540
 	private function funcAnd(array $args, CellAddress $address): CellValue {
537
-		if (sizeof($args) == 0) {
541
+		if (count($args) === 0) {
538 542
 			throw new CellEvaluationException("AND requires one or more arguments");
539 543
 		}
540
-		$values = array_map(fn($arg) => $this->evaluate($arg, $address), $args);
544
+		$values = $this->flattenedNumericArguments('AND', $args, $address, false);
541 545
 		foreach ($values as $value) {
542 546
 			$result = $value->booleanValue();
543 547
 			if ($result === null) {
@@ -622,7 +626,7 @@ class CellExpressionSet {
622 626
 	 * @return CellValue  result
623 627
 	 */
624 628
 	private function funcIf(array $args, CellAddress $address): CellValue {
625
-		if (sizeof($args) != 3) {
629
+		if (count($args) !== 3) {
626 630
 			throw new CellEvaluationException("IF expects three arguments");
627 631
 		}
628 632
 		$evaled = array_map(fn($arg) => $this->evaluate($arg, $address), $args);
@@ -648,14 +652,14 @@ class CellExpressionSet {
648 652
 	 * @return CellValue  result
649 653
 	 */
650 654
 	private function funcIfs(array $args, CellAddress $address): CellValue {
651
-		if (sizeof($args) < 3) {
655
+		if (count($args) < 3) {
652 656
 			throw new CellEvaluationException("IFS expects at least 3 arguments");
653 657
 		}
654
-		if ((sizeof($args) % 2) != 1) {
658
+		if ((count($args) % 2) !== 1) {
655 659
 			throw new CellEvaluationException("IFS expects an odd number of arguments");
656 660
 		}
657 661
 		$evaled = array_map(fn($arg) => $this->evaluate($arg, $address), $args);
658
-		for ($i = 0; $i < sizeof($evaled) - 1; $i += 2) {
662
+		for ($i = 0; $i < count($evaled) - 1; $i += 2) {
659 663
 			$test = $evaled[$i].booleanValue();
660 664
 			if ($test === null) {
661 665
 				throw new CellEvaluationException("IFS expects a boolean for argument " . ($i + 1));
@@ -664,7 +668,7 @@ class CellExpressionSet {
664 668
 				return $evaled[$i + 1];
665 669
 			}
666 670
 		}
667
-		return $evaled[sizeof($evaled) - 1];
671
+		return $evaled[count($evaled) - 1];
668 672
 	}
669 673
 
670 674
 	/**
@@ -690,7 +694,7 @@ class CellExpressionSet {
690 694
 	private function funcLog(array $args, CellAddress $address): CellValue {
691 695
 		$evaled = $this->assertNumericArguments('LOG', 1, 2, $args, $address);
692 696
 		$exponent = $evaled[0];
693
-		$base = (sizeof($evaled) > 1) ? $evaled[1]->value : 10.0;
697
+		$base = (count($evaled) > 1) ? $evaled[1]->value : 10.0;
694 698
 		$newValue = log($exponent->value) / log($base);
695 699
 		return CellValue::fromValue($newValue, $exponent->type);
696 700
 	}
@@ -703,7 +707,7 @@ class CellExpressionSet {
703 707
 	 * @return CellValue  result
704 708
 	 */
705 709
 	private function funcLower(array $args, CellAddress $address): CellValue {
706
-		if (sizeof($args) != 1) {
710
+		if (count($args) !== 1) {
707 711
 			throw new CellEvaluationException("LOWER requires one argument");
708 712
 		}
709 713
 		$evaled = array_map(fn($arg) => $this->evaluate($arg, $address), $args);
@@ -724,7 +728,7 @@ class CellExpressionSet {
724 728
 	private function funcMax(array $args, CellAddress $address): CellValue {
725 729
 		$maxValue = null;
726 730
 		$flattened = $this->flattenedNumericArguments('MAX', $args, $address);
727
-		if (sizeof($flattened) == 0) {
731
+		if (count($flattened) === 0) {
728 732
 			throw new CellEvaluationException("MAX requires at least one numeric argument");
729 733
 		}
730 734
 		foreach ($flattened as $argument) {
@@ -745,7 +749,7 @@ class CellExpressionSet {
745 749
 	private function funcMin(array $args, CellAddress $address): CellValue {
746 750
 		$minValue = null;
747 751
 		$flattened = $this->flattenedNumericArguments('MIN', $args, $address);
748
-		if (sizeof($flattened) == 0) {
752
+		if (count($flattened) === 0) {
749 753
 			throw new CellEvaluationException("MIN requires at least one numeric argument");
750 754
 		}
751 755
 		foreach ($flattened as $argument) {
@@ -764,7 +768,7 @@ class CellExpressionSet {
764 768
 	 * @return CellValue  result
765 769
 	 */
766 770
 	private function funcMod(array $args, CellAddress $address): CellValue {
767
-		if (sizeof($args) != 2) {
771
+		if (count($args) !== 2) {
768 772
 			throw new CellEvaluationException("MOD requires two numeric arguments");
769 773
 		}
770 774
 		$values = array_map(fn($arg) => $this->evaluate($arg, $address), $args);
@@ -779,7 +783,7 @@ class CellExpressionSet {
779 783
 	 * @return CellValue  result
780 784
 	 */
781 785
 	private function funcNot(array $args, CellAddress $address): CellValue {
782
-		if (sizeof($args) != 1) {
786
+		if (count($args) !== 1) {
783 787
 			throw new CellEvaluationException("NOT expects one argument");
784 788
 		}
785 789
 		$evaled = array_map(fn($arg) => $this->evaluate($arg, $address), $args);
@@ -798,10 +802,10 @@ class CellExpressionSet {
798 802
 	 * @return CellValue  result
799 803
 	 */
800 804
 	private function funcOr(array $args, CellAddress $address): CellValue {
801
-		if (sizeof($args) == 0) {
805
+		if (count($args) === 0) {
802 806
 			throw new CellEvaluationException("OR requires one or more arguments");
803 807
 		}
804
-		$values = array_map(fn($arg) => $this->evaluate($arg, $address), $args);
808
+		$values = $this->flattenedNumericArguments('OR', $args, $address, false);
805 809
 		foreach ($values as $value) {
806 810
 			$result = $value->booleanValue();
807 811
 			if ($result === null) {
@@ -839,7 +843,7 @@ class CellExpressionSet {
839 843
 	private function funcRound(array $args, CellAddress $address): CellValue {
840 844
 		$evaled = $this->assertNumericArguments('ROUND', 1, 2, $args, $address);
841 845
 		$val = $evaled[0];
842
-		$places = sizeof($evaled) > 1 ? $evaled[1]->value : 0;
846
+		$places = count($evaled) > 1 ? $evaled[1]->value : 0;
843 847
 		$divider = pow(10.0, $places);
844 848
 		$newValue = round($val->value * $divider) / $divider;
845 849
 		return CellValue::fromValue($newValue, $val->type);
@@ -867,7 +871,7 @@ class CellExpressionSet {
867 871
 	 * @return CellValue  result
868 872
 	 */
869 873
 	private function funcSubstitute(array $args, CellAddress $address): CellValue {
870
-		if (sizeof($args) != 3) {
874
+		if (count($args) !== 3) {
871 875
 			throw new CellEvaluationException("SUBSTITUTE expects 3 string arguments");
872 876
 		}
873 877
 		$values = array_map(fn($arg) => $this->evaluate($arg, $address), $args);
@@ -911,7 +915,7 @@ class CellExpressionSet {
911 915
 	 * @return CellValue  result
912 916
 	 */
913 917
 	private function funcUpper(array $args, CellAddress $address): CellValue {
914
-		if (sizeof($args) != 1) {
918
+		if (count($args) !== 1) {
915 919
 			throw new CellEvaluationException("UPPER requires one argument");
916 920
 		}
917 921
 		$evaled = array_map(fn($arg) => $this->evaluate($arg, $address), $args);
@@ -930,10 +934,10 @@ class CellExpressionSet {
930 934
 	 * @return CellValue  result
931 935
 	 */
932 936
 	private function funcXor(array $args, CellAddress $address): CellValue {
933
-		if (sizeof($args) == 0) {
937
+		if (count($args) === 0) {
934 938
 			throw new CellEvaluationException("XOR requires one or more arguments");
935 939
 		}
936
-		$values = array_map(fn($arg) => $this->evaluate($arg, $address), $args);
940
+		$values = $this->flattenedNumericArguments('XOR', $args, $address, false);
937 941
 		$result = null;
938 942
 		foreach ($values as $value) {
939 943
 			$b = $value->booleanValue();
@@ -942,7 +946,7 @@ class CellExpressionSet {
942 946
 			}
943 947
 			$result = ($result === null) ? $b : ($result ^ $b);
944 948
 		}
945
-		return CellValue::fromValue($result != 0);
949
+		return CellValue::fromValue($result !== 0);
946 950
 	}
947 951
 }
948 952
 
@@ -1009,7 +1013,7 @@ class CellExpression {
1009 1013
 	 */
1010 1014
 	public static function parse(string $expression, CellAddress $address): ?CellExpression {
1011 1015
 		$tokens = self::expressionToTokens($expression);
1012
-		if (sizeof($tokens) == 0) return null;
1016
+		if (count($tokens) === 0) return null;
1013 1017
 		$expr = self::expressionFromTokens($tokens, $address);
1014 1018
 		$expr->location = $address;
1015 1019
 		return $expr;
@@ -1022,7 +1026,7 @@ class CellExpression {
1022 1026
 	 * @param string $indent
1023 1027
 	 */
1024 1028
 	public static function dumpExpression(CellExpression $expression, string $indent = '') {
1025
-		if (sizeof($expression->arguments) == 0) {
1029
+		if (count($expression->arguments) === 0) {
1026 1030
 			error_log($indent . "expr " . $expression->op->name . "()");
1027 1031
 		} else {
1028 1032
 			error_log($indent . $expression->op->name . '(');
@@ -1141,7 +1145,7 @@ class CellExpression {
1141 1145
 		$l = mb_strlen($text);
1142 1146
 		while ($pos < $l) {
1143 1147
 			$ch = mb_substr($text, $pos, 1);
1144
-			if ($ch == ' ' || $ch == "\t" || $ch == "\n" || $ch == "\r") {
1148
+			if ($ch === ' ' || $ch === "\t" || $ch === "\n" || $ch === "\r") {
1145 1149
 				$pos++;
1146 1150
 			} else {
1147 1151
 				return;
@@ -1155,7 +1159,7 @@ class CellExpression {
1155 1159
 		$l = mb_strlen($target);
1156 1160
 		if ($l > $remaining) return null;
1157 1161
 		$test = mb_substr($text, $pos, $l);
1158
-		if (mb_strtoupper($test) != mb_strtoupper($target)) return null;
1162
+		if (mb_strtoupper($test) !== mb_strtoupper($target)) return null;
1159 1163
 		$pos += $l;
1160 1164
 		return new CellExpressionToken($type, $test);
1161 1165
 	}
@@ -1165,7 +1169,7 @@ class CellExpression {
1165 1169
 		$ch = mb_substr($text, $p, 1);
1166 1170
 		$address = '';
1167 1171
 		$isName = true;
1168
-		if ($ch == '$') {
1172
+		if ($ch === '$') {
1169 1173
 			$address .= $ch;
1170 1174
 			$isName = false;
1171 1175
 			$p++;
@@ -1174,15 +1178,15 @@ class CellExpression {
1174 1178
 		if ($col === null) return null;
1175 1179
 		$address .= $col;
1176 1180
 		$ch = mb_substr($text, $p, 1);
1177
-		if ($ch == '$') {
1181
+		if ($ch === '$') {
1178 1182
 			$address .= $ch;
1179 1183
 			$isName = false;
1180 1184
 			$p++;
1181
-			$row = self::readChars($text, $p, fn($ch) => self::isDigit($ch), 1);
1185
+			$row = self::readChars($text, $p, fn($s) => self::isDigit($s), 1);
1182 1186
 			if ($row === null) return null;
1183 1187
 			$address .= $row;
1184 1188
 		} else {
1185
-			$row = self::readChars($text, $p, fn($ch) => self::isDigit($ch), 0);
1189
+			$row = self::readChars($text, $p, fn($s) => self::isDigit($s), 0);
1186 1190
 			if ($row === null) return null;
1187 1191
 			$address .= $row;
1188 1192
 		}
@@ -1220,7 +1224,7 @@ class CellExpression {
1220 1224
 		}
1221 1225
 		if ($pos < $l) {
1222 1226
 			$ch = mb_substr($text, $pos, 1);
1223
-			if ($ch == '.') {
1227
+			if ($ch === '.') {
1224 1228
 				$numStr .= $ch;
1225 1229
 				$pos++;
1226 1230
 				while ($pos < $l) {
@@ -1249,14 +1253,14 @@ class CellExpression {
1249 1253
 			$pos++;
1250 1254
 			if ($inEscape) {
1251 1255
 				$inEscape = false;
1252
-				if ($ch == '\\' || $ch == '"') {
1256
+				if ($ch === '\\' || $ch === '"') {
1253 1257
 					$str .= $ch;
1254 1258
 				} else {
1255 1259
 					throw new CellSyntaxException("Bad string escape sequence \"\\{$ch}\"");
1256 1260
 				}
1257
-			} elseif ($ch == '\\') {
1261
+			} elseif ($ch === '\\') {
1258 1262
 				$inEscape = true;
1259
-			} elseif ($ch == '"') {
1263
+			} elseif ($ch === '"') {
1260 1264
 				return new CellExpressionToken(CellExpressionTokenType::String, $str);
1261 1265
 			} else {
1262 1266
 				$str .= $ch;
@@ -1324,9 +1328,9 @@ class CellExpression {
1324 1328
 	 * @return ?CellExpression
1325 1329
 	 */
1326 1330
 	public static function expressionFromTokens(array $tokens, CellAddress $address): ?CellExpression {
1327
-		if ($expr = self::tryExpressionAndFormat($tokens, 0, sizeof($tokens) - 1, $address)) return $expr;
1328
-		if ($expr = self::tryExpressionAndFill($tokens, 0, sizeof($tokens) - 1, $address)) return $expr;
1329
-		if ($expr = self::tryExpression($tokens, 0, sizeof($tokens) - 1, $address)) return $expr;
1331
+		if ($expr = self::tryExpressionAndFormat($tokens, 0, count($tokens) - 1, $address)) return $expr;
1332
+		if ($expr = self::tryExpressionAndFill($tokens, 0, count($tokens) - 1, $address)) return $expr;
1333
+		if ($expr = self::tryExpression($tokens, 0, count($tokens) - 1, $address)) return $expr;
1330 1334
 		return null;
1331 1335
 	}
1332 1336
 
@@ -1340,7 +1344,7 @@ class CellExpression {
1340 1344
 	private static function tryExpressionAndFormat(array $tokens, int $start,
1341 1345
 			int $end, CellAddress $address): ?CellExpression {
1342 1346
 		for ($t = $start + 1; $t < $end; $t++) {
1343
-			if ($tokens[$t]->type == CellExpressionTokenType::Semicolon) {
1347
+			if ($tokens[$t]->type === CellExpressionTokenType::Semicolon) {
1344 1348
 				$expr = self::tryExpressionAndFill($tokens, $start, $t - 1, $address) ??
1345 1349
 					self::tryExpression($tokens, $start, $t - 1, $address);
1346 1350
 				if ($expr === null) return null;
@@ -1366,7 +1370,7 @@ class CellExpression {
1366 1370
 		if ($count < 2) return null;
1367 1371
 		if (!$tokens[$end]->type->isPotentialName()) return null;
1368 1372
 		$name = mb_strtoupper($tokens[$end]->content);
1369
-		if ($name != 'FILL') return null;
1373
+		if ($name !== 'FILL') return null;
1370 1374
 		$exp = self::tryExpression($tokens, $start, $end - 1, $address);
1371 1375
 		$columnIndex = $address->columnIndex;
1372 1376
 		$exp->fillRanges = [
@@ -1396,10 +1400,10 @@ class CellExpression {
1396 1400
 		$count = $end - $start + 1;
1397 1401
 		if ($count < 0 || $count > 2) return null;
1398 1402
 		if (!$tokens[$start]->type->isPotentialName()) return null;
1399
-		$type = mb_strtolower($tokens[$start]);
1403
+		$type = mb_strtolower($tokens[$start]->content);
1400 1404
 		if (!CellValue::isTypeNumeric($type)) return null;
1401 1405
 		if ($count > 1) {
1402
-			if ($tokens[$start + 1]->type != CellExpressionTokenType::Number) return null;
1406
+			if ($tokens[$start + 1]->type !== CellExpressionTokenType::Number) return null;
1403 1407
 			$decimals = intval($tokens[$start + 1]->content);
1404 1408
 		} else {
1405 1409
 			$decimals = null;
@@ -1437,18 +1441,18 @@ class CellExpression {
1437 1441
 	 */
1438 1442
 	private static function tryParenExpression(array $tokens, int $start,
1439 1443
 			int $end, CellAddress $address): ?CellExpression {
1440
-		if ($tokens[$start]->type != CellExpressionTokenType::OpenParen) return null;
1441
-		if ($tokens[$end]->type != CellExpressionTokenType::CloseParen) return null;
1444
+		if ($tokens[$start]->type !== CellExpressionTokenType::OpenParen) return null;
1445
+		if ($tokens[$end]->type !== CellExpressionTokenType::CloseParen) return null;
1442 1446
 		$parenLevel = 0;
1443 1447
 		for ($t = $start + 1; $t < $end; $t++) {
1444
-			if ($tokens[$t]->type == CellExpressionTokenType::OpenParen) {
1448
+			if ($tokens[$t]->type === CellExpressionTokenType::OpenParen) {
1445 1449
 				$parenLevel++;
1446
-			} elseif ($tokens[$t]->type == CellExpressionTokenType::CloseParen) {
1450
+			} elseif ($tokens[$t]->type === CellExpressionTokenType::CloseParen) {
1447 1451
 				$parenLevel--;
1448 1452
 			}
1449 1453
 			if ($parenLevel < 0) return null;
1450 1454
 		}
1451
-		if ($parenLevel != 0) return null;
1455
+		if ($parenLevel !== 0) return null;
1452 1456
 		return self::tryExpression($tokens, $start + 1, $end - 1, $address);
1453 1457
 	}
1454 1458
 
@@ -1461,11 +1465,11 @@ class CellExpression {
1461 1465
 	 */
1462 1466
 	private static function tryNumber(array $tokens, int $start, int $end,
1463 1467
 			CellAddress $address): ?CellExpression {
1464
-		if ($tokens[$end]->type != CellExpressionTokenType::Number) return null;
1468
+		if ($tokens[$end]->type !== CellExpressionTokenType::Number) return null;
1465 1469
 		if ($end > $start + 1) return null;
1466 1470
 		$val = CellValue::fromCellString($tokens[$end]->content);
1467 1471
 		if ($end > $start) {
1468
-			if ($tokens[$start]->type != CellExpressionTokenType::Minus) return null;
1472
+			if ($tokens[$start]->type !== CellExpressionTokenType::Minus) return null;
1469 1473
 			$val->value = -$val->value;
1470 1474
 		}
1471 1475
 		return new CellExpression(CellExpressionOperation::Number, [ $val ]);
@@ -1480,8 +1484,8 @@ class CellExpression {
1480 1484
 	 */
1481 1485
 	private static function tryString(array $tokens, int $start, int $end,
1482 1486
 			CellAddress $address): ?CellExpression {
1483
-		if ($start != $end) return null;
1484
-		if ($tokens[$start]->type != CellExpressionTokenType::String) return null;
1487
+		if ($start !== $end) return null;
1488
+		if ($tokens[$start]->type !== CellExpressionTokenType::String) return null;
1485 1489
 		$str = $tokens[$start]->content;
1486 1490
 		return new CellExpression(CellExpressionOperation::String,
1487 1491
 			[ new CellValue($str, $str, CellValue::TYPE_STRING, 0) ]);
@@ -1496,12 +1500,12 @@ class CellExpression {
1496 1500
 	 */
1497 1501
 	private static function tryBoolean(array $tokens, int $start, int $end,
1498 1502
 			CellAddress $address): ?CellExpression {
1499
-		if ($start != $end) return null;
1503
+		if ($start !== $end) return null;
1500 1504
 		if (!$tokens[$start]->type->isPotentialName()) return null;
1501 1505
 		$str = mb_strtoupper($tokens[$start]->content);
1502
-		if ($str != 'TRUE' && $str != 'FALSE') return null;
1506
+		if ($str !== 'TRUE' && $str !== 'FALSE') return null;
1503 1507
 		return new CellExpression(CellExpressionOperation::Boolean,
1504
-			[ new CellValue($str, $str == 'TRUE', CellValue::TYPE_BOOLEAN) ]);
1508
+			[ new CellValue($str, $str === 'TRUE', CellValue::TYPE_BOOLEAN) ]);
1505 1509
 	}
1506 1510
 
1507 1511
 	/**
@@ -1517,8 +1521,8 @@ class CellExpression {
1517 1521
 		if ($count < 3) return null;
1518 1522
 		if (!$tokens[$start]->type->isPotentialName()) return null;
1519 1523
 		$qualifier = $tokens[$start]->content;
1520
-		if ($tokens[$start + 1]->type != CellExpressionTokenType::OpenParen) return null;
1521
-		if ($tokens[$end]->type != CellExpressionTokenType::CloseParen) return null;
1524
+		if ($tokens[$start + 1]->type !== CellExpressionTokenType::OpenParen) return null;
1525
+		if ($tokens[$end]->type !== CellExpressionTokenType::CloseParen) return null;
1522 1526
 		$argList = self::tryArgumentList($tokens, $start + 2, $end - 1, $address);
1523 1527
 		if ($argList === null) return null;
1524 1528
 		return new CellExpression(CellExpressionOperation::Function, $argList, $qualifier);
@@ -1534,7 +1538,7 @@ class CellExpression {
1534 1538
 	private static function tryArgumentList(array $tokens, int $start, int $end,
1535 1539
 			CellAddress $address): ?array {
1536 1540
 		$count = $end - $start + 1;
1537
-		if ($count == 0) return [];
1541
+		if ($count === 0) return [];
1538 1542
 		$parenDepth = 0;
1539 1543
 		$argCount = 1;
1540 1544
 
@@ -1543,11 +1547,11 @@ class CellExpression {
1543 1547
 		$argTokens = []; // argindex -> [ start, end ]
1544 1548
 		$exprStartToken = $start;
1545 1549
 		for ($i = $start; $i <= $end; $i++) {
1546
-			if ($tokens[$i]->type == CellExpressionTokenType::OpenParen) {
1550
+			if ($tokens[$i]->type === CellExpressionTokenType::OpenParen) {
1547 1551
 				$parenDepth++;
1548
-			} elseif ($tokens[$i]->type == CellExpressionTokenType::CloseParen) {
1552
+			} elseif ($tokens[$i]->type === CellExpressionTokenType::CloseParen) {
1549 1553
 				$parenDepth--;
1550
-			} elseif ($tokens[$i]->type == CellExpressionTokenType::Comma && $parenDepth == 0) {
1554
+			} elseif ($tokens[$i]->type === CellExpressionTokenType::Comma && $parenDepth === 0) {
1551 1555
 				$exprEndToken = $i - 1;
1552 1556
 				array_push($argTokens, [ $exprStartToken, $exprEndToken ]);
1553 1557
 				$exprStartToken = $i + 1;
@@ -1575,10 +1579,10 @@ class CellExpression {
1575 1579
 	private static function tryRange(array $tokens, int $start, int $end,
1576 1580
 			CellAddress $address): ?CellExpression {
1577 1581
 		$count = $end - $start + 1;
1578
-		if ($count != 3) return null;
1582
+		if ($count !== 3) return null;
1579 1583
 		if (!$tokens[$start]->type->isPotentialAddress()) return null;
1580 1584
 		$first = mb_strtoupper($tokens[$start]->content);
1581
-		if ($tokens[$start + 1]->type != CellExpressionTokenType::Colon) return null;
1585
+		if ($tokens[$start + 1]->type !== CellExpressionTokenType::Colon) return null;
1582 1586
 		if (!$tokens[$end]->type->isPotentialAddress()) return null;
1583 1587
 		$last = mb_strtoupper($tokens[$end]->content);
1584 1588
 		$firstAddress = CellAddress::fromString($first);
@@ -1596,7 +1600,7 @@ class CellExpression {
1596 1600
 	 */
1597 1601
 	private static function tryReference(array $tokens, int $start, int $end,
1598 1602
 			CellAddress $address): ?CellExpression {
1599
-		if ($start != $end) return null;
1603
+		if ($start !== $end) return null;
1600 1604
 		if (!$tokens[$start]->type->isPotentialAddress()) return null;
1601 1605
 		$ref = mb_strtoupper($tokens[$start]->content);
1602 1606
 		$refAddress = CellAddress::fromString($ref, $address, true);
@@ -1631,11 +1635,11 @@ class CellExpression {
1631 1635
 		$candidates = [];
1632 1636
 		$parenLevel = 0;
1633 1637
 		for ($i = $start; $i <= $end; $i++) {
1634
-			if ($tokens[$i]->type == CellExpressionTokenType::OpenParen) {
1638
+			if ($tokens[$i]->type === CellExpressionTokenType::OpenParen) {
1635 1639
 				$parenLevel++;
1636
-			} elseif ($tokens[$i]->type == CellExpressionTokenType::CloseParen) {
1640
+			} elseif ($tokens[$i]->type === CellExpressionTokenType::CloseParen) {
1637 1641
 				$parenLevel--;
1638
-			} elseif ($parenLevel == 0 && $i > $start && $i < $end) {
1642
+			} elseif ($parenLevel === 0 && $i > $start && $i < $end) {
1639 1643
 				$op = $tokens[$i]->type->name;
1640 1644
 				$priority = self::infixPriority[$op] ?? false;
1641 1645
 				if ($priority === false) continue;
@@ -1706,7 +1710,7 @@ class CellExpression {
1706 1710
 			[ CellExpressionTokenType::Not, CellExpressionOperation::UnaryNot ],
1707 1711
 		];
1708 1712
 		foreach ($ops as $op) {
1709
-			if ($tokens[$start]->type != $op[0]) continue;
1713
+			if ($tokens[$start]->type !== $op[0]) continue;
1710 1714
 			$operand = self::tryExpression($tokens, $start + 1, $end, $address);
1711 1715
 			if ($operand === null) return null;
1712 1716
 			return new CellExpression($op[1], [ $operand ]);
@@ -1805,7 +1809,7 @@ class CellAddress {
1805 1809
 	 * Tests if a string is formatted like an address.
1806 1810
 	 */
1807 1811
 	public static function isAddress(string $text): bool {
1808
-		return self::fromString($text) != null;
1812
+		return self::fromString($text) !== null;
1809 1813
 	}
1810 1814
 
1811 1815
 	/**
@@ -1847,7 +1851,7 @@ class CellAddress {
1847 1851
 		if (!$this->isResolved && $resolveToRow) {
1848 1852
 			$newRowIndex = $relativeFrom->rowIndex;
1849 1853
 		}
1850
-		if ($newRowIndex != -1 && !$this->isRowFixed) {
1854
+		if ($newRowIndex !== -1 && !$this->isRowFixed) {
1851 1855
 			$rowDelta = $relativeTo->rowIndex - $relativeFrom->rowIndex;
1852 1856
 			$newRowIndex += $rowDelta;
1853 1857
 		}
@@ -1857,12 +1861,12 @@ class CellAddress {
1857 1861
 
1858 1862
 	public function equals($other): bool {
1859 1863
 		if (!($other instanceof CellAddress)) return false;
1860
-		return $other->columnIndex == $this->columnIndex && $other->rowIndex == $this->rowIndex;
1864
+		return $other->columnIndex === $this->columnIndex && $other->rowIndex === $this->rowIndex;
1861 1865
 	}
1862 1866
 
1863 1867
 	public function exactlyEquals($other): bool {
1864 1868
 		if (!($other instanceof CellAddress)) return false;
1865
-		return $other->name == $this->name;
1869
+		return $other->name === $this->name;
1866 1870
 	}
1867 1871
 
1868 1872
 	public function __toString(): string {
@@ -1927,12 +1931,12 @@ class CellAddress {
1927 1931
 			if ($throwIfInvalid) throw new CellEvaluationException("Bad address \"{$address}\"", '#REF');
1928 1932
 			return null;
1929 1933
 		}
1930
-		$isColumnFixed = ($groups[1] == '$');
1934
+		$isColumnFixed = ($groups[1] === '$');
1931 1935
 		$letters = mb_strtoupper($groups[2]);
1932
-		$isRowFixed = ($groups[3] == '$');
1936
+		$isRowFixed = ($groups[3] === '$');
1933 1937
 		$numbers = $groups[4];
1934 1938
 		$columnIndex = self::lettersToColumnIndex($letters);
1935
-		$rowIndex = (mb_strlen($numbers) == 0) ? -1 : intval($numbers) - 1;
1939
+		$rowIndex = (mb_strlen($numbers) === 0) ? -1 : intval($numbers) - 1;
1936 1940
 		if ($columnIndex < 0 && $relativeTo !== null) $columnIndex = $relativeTo->columnIndex;
1937 1941
 		if ($rowIndex < 0 && $relativeTo !== null) $rowIndex = $relativeTo->rowIndex;
1938 1942
 		return new CellAddress($columnIndex, $rowIndex, $isColumnFixed, $isRowFixed);
@@ -1958,7 +1962,7 @@ class CellAddressRange {
1958 1962
 	 * @param CellAddress $toCell
1959 1963
 	 */
1960 1964
 	public function __construct(CellAddress $fromCell, CellAddress $toCell) {
1961
-		if ($fromCell->isResolved != $toCell->isResolved) {
1965
+		if ($fromCell->isResolved !== $toCell->isResolved) {
1962 1966
 			throw new CellEvaluationException("Cannot mix resolved and unresolved cell addresses in range: {$fromCell->name} and {$toCell->name}");
1963 1967
 		}
1964 1968
 		$this->minColumnIndex = min($fromCell->columnIndex, $toCell->columnIndex);
@@ -1988,41 +1992,41 @@ class CellAddressRange {
1988 1992
 	 * @return object iterable object
1989 1993
 	 */
1990 1994
 	public function cellsIn(SpreadsheetGrid $grid): Iterator {
1991
-		$min_col = $this->minColumnIndex;
1992
-		$max_col = $this->maxColumnIndex;
1993
-		$min_row = $this->minRowIndex;
1994
-		$max_row = $this->maxRowIndex;
1995
-		if ($min_row == -1) {
1996
-			$min_row = 0;
1997
-		}
1998
-		if ($max_row == -1) {
1999
-			$max_row = $grid->rowCount - 1;
2000
-		}
2001
-		$max_col = min($max_col, $grid->columnCount);
2002
-		return new class($grid, $min_col, $max_col, $min_row, $max_row) implements Iterator {
1995
+		$minCol = $this->minColumnIndex;
1996
+		$maxCol = $this->maxColumnIndex;
1997
+		$minRow = $this->minRowIndex;
1998
+		$maxRow = $this->maxRowIndex;
1999
+		if ($minRow === -1) {
2000
+			$minRow = 0;
2001
+		}
2002
+		if ($maxRow === -1) {
2003
+			$maxRow = $grid->rowCount - 1;
2004
+		}
2005
+		$maxCol = min($maxCol, $grid->columnCount);
2006
+		return new class($grid, $minCol, $maxCol, $minRow, $maxRow) implements Iterator {
2003 2007
 			private SpreadsheetGrid $grid;
2004
-			private int $min_col;
2005
-			private int $max_col;
2006
-			private int $min_row;
2007
-			private int $max_row;
2008
+			private int $minCol;
2009
+			private int $maxCol;
2010
+			private int $minRow;
2011
+			private int $maxRow;
2008 2012
 			private int $col;
2009 2013
 			private int $row;
2010 2014
 			private ?CellAddress $address = null;
2011 2015
 			private ?SpreadsheetCell $cell = null;
2012
-			function __construct($grid, $min_col, $max_col, $min_row, $max_row) {
2016
+			function __construct($grid, $minCol, $maxCol, $minRow, $maxRow) {
2013 2017
 				$this->grid = $grid;
2014
-				$this->min_col = $min_col;
2015
-				$this->max_col = $max_col;
2016
-				$this->min_row = $min_row;
2017
-				$this->max_row = $max_row;
2018
-				$this->col = $min_col;
2019
-				$this->row = $min_row;
2018
+				$this->minCol = $minCol;
2019
+				$this->maxCol = $maxCol;
2020
+				$this->minRow = $minRow;
2021
+				$this->maxRow = $maxRow;
2022
+				$this->col = $minCol;
2023
+				$this->row = $minRow;
2020 2024
 				$this->setValues();
2021 2025
 			}
2022 2026
 
2023 2027
 			private function setValues() {
2024
-				if ($this->col >= $this->min_col && $this->col <= $this->max_col &&
2025
-						$this->row >= $this->min_row && $this->row <= $this->max_row) {
2028
+				if ($this->col >= $this->minCol && $this->col <= $this->maxCol &&
2029
+						$this->row >= $this->minRow && $this->row <= $this->maxRow) {
2026 2030
 					$this->address = new CellAddress($this->col, $this->row);
2027 2031
 					$this->cell = $this->grid->cellAt($this->address);
2028 2032
 					if (!$this->cell) {
@@ -2036,8 +2040,8 @@ class CellAddressRange {
2036 2040
 
2037 2041
 			private function increment(): void {
2038 2042
 				$this->row++;
2039
-				if ($this->row > $this->max_row) {
2040
-					$this->row = $this->min_row;
2043
+				if ($this->row > $this->maxRow) {
2044
+					$this->row = $this->minRow;
2041 2045
 					$this->col++;
2042 2046
 				}
2043 2047
 				$this->setValues();
@@ -2056,13 +2060,13 @@ class CellAddressRange {
2056 2060
 			}
2057 2061
 
2058 2062
 			function rewind(): void {
2059
-				$this->row = $this->min_row;
2060
-				$this->col = $this->min_col;
2063
+				$this->row = $this->minRow;
2064
+				$this->col = $this->minCol;
2061 2065
 				$this->setValues();
2062 2066
 			}
2063 2067
 
2064 2068
 			function valid(): bool {
2065
-				return $this->address != null;
2069
+				return $this->address !== null;
2066 2070
 			}
2067 2071
 		};
2068 2072
 	}
@@ -2188,8 +2192,8 @@ class CellValue {
2188 2192
 		if (is_numeric($value)) {
2189 2193
 			$resolvedType = $type ?? CellValue::TYPE_NUMBER;
2190 2194
 			$resolvedDecimals = ($decimals !== null) ? $decimals :
2191
-				($resolvedType == CellValue::TYPE_CURRENCY ? 2 :
2192
-					CellValue::autodecimals($resolvedType == CellValue::TYPE_PERCENT ?
2195
+				($resolvedType === CellValue::TYPE_CURRENCY ? 2 :
2196
+					CellValue::autodecimals($resolvedType === CellValue::TYPE_PERCENT ?
2193 2197
 						$value * 100.0 : $value));
2194 2198
 			$formatted = CellValue::formatType($value, $resolvedType, $resolvedDecimals);
2195 2199
 			return new CellValue($formatted, $value, $resolvedType, $resolvedDecimals);
@@ -2208,7 +2212,7 @@ class CellValue {
2208 2212
 		$cellString = ($cellString !== null) ? trim($cellString) : null;
2209 2213
 		$this->formattedValue = $cellString;
2210 2214
 		// blank
2211
-		if ($cellString === null || $cellString == '') {
2215
+		if ($cellString === null || $cellString === '') {
2212 2216
 			$this->type = CellValue::TYPE_BLANK;
2213 2217
 			$this->value = null;
2214 2218
 			return;
@@ -2223,7 +2227,7 @@ class CellValue {
2223 2227
 		}
2224 2228
 		// =TRUE
2225 2229
 		$caps = mb_strtoupper($cellString);
2226
-		if ($caps == 'TRUE') {
2230
+		if ($caps === 'TRUE') {
2227 2231
 			$this->type = CellValue::TYPE_BOOLEAN;
2228 2232
 			$this->formattedValue = $caps;
2229 2233
 			$this->value = true;
@@ -2231,7 +2235,7 @@ class CellValue {
2231 2235
 			return;
2232 2236
 		}
2233 2237
 		// =FALSE
2234
-		if ($caps == 'FALSE') {
2238
+		if ($caps === 'FALSE') {
2235 2239
 			$this->type = CellValue::TYPE_BOOLEAN;
2236 2240
 			$this->formattedValue = $caps;
2237 2241
 			$this->value = false;
@@ -2319,7 +2323,7 @@ class CellValue {
2319 2323
 			case CellValue::TYPE_CURRENCY:
2320 2324
 			case CellValue::TYPE_NUMBER:
2321 2325
 			case CellValue::TYPE_PERCENT:
2322
-				return $this->value != 0;
2326
+				return $this->value !== 0;
2323 2327
 			case CellValue::TYPE_ERROR:
2324 2328
 			case CellValue::TYPE_FORMULA:
2325 2329
 			case CellValue::TYPE_STRING:
@@ -2401,7 +2405,7 @@ class CellValue {
2401 2405
 	 */
2402 2406
 	public function divide(CellValue $b): CellValue {
2403 2407
 		return self::binaryNumericOperation($this, $b, '/', function($aVal, $bVal) {
2404
-			if ($bVal == 0) throw new CellEvaluationException("Division by zero", '#NAN');
2408
+			if ($bVal === 0) throw new CellEvaluationException("Division by zero", '#NAN');
2405 2409
 			return $aVal / $bVal;
2406 2410
 		});
2407 2411
 	}
@@ -2413,7 +2417,7 @@ class CellValue {
2413 2417
 	 */
2414 2418
 	public function modulo($b) {
2415 2419
 		return self::binaryNumericOperation($this, $b, '%', function($aVal, $bVal) {
2416
-			if ($bVal == 0) throw new CellEvaluationException("Division by zero", '#NAN');
2420
+			if ($bVal === 0) throw new CellEvaluationException("Division by zero", '#NAN');
2417 2421
 			return $aVal % $bVal;
2418 2422
 		});
2419 2423
 	}
@@ -2450,14 +2454,14 @@ class CellValue {
2450 2454
 	 * Returns the result of whether this value is equal to `$b`.
2451 2455
 	 */
2452 2456
 	public function eq(CellValue $b): CellValue {
2453
-		return self::fromValue(CellValue::compare($this, $b) == 0);
2457
+		return self::fromValue(CellValue::compare($this, $b) === 0);
2454 2458
 	}
2455 2459
 
2456 2460
 	/**
2457 2461
 	 * Returns the result of whether this value is unequal to `$b`.
2458 2462
 	 */
2459 2463
 	public function neq(CellValue $b): CellValue {
2460
-		return self::fromValue(CellValue::compare($this, $b) != 0);
2464
+		return self::fromValue(CellValue::compare($this, $b) !== 0);
2461 2465
 	}
2462 2466
 
2463 2467
 	/**
@@ -2470,7 +2474,7 @@ class CellValue {
2470 2474
 			case CellValue::TYPE_CURRENCY:
2471 2475
 			case CellValue::TYPE_NUMBER:
2472 2476
 			case CellValue::TYPE_PERCENT:
2473
-				return self::fromValue($this->value == 0);
2477
+				return self::fromValue($this->value === 0);
2474 2478
 			case CellValue::TYPE_STRING:
2475 2479
 				throw new CellEvaluationException("Cannot perform NOT on string");
2476 2480
 			case CellValue::TYPE_BOOLEAN:
@@ -2528,24 +2532,24 @@ class CellValue {
2528 2532
 	 * @throws CellEvaluationException  if types are incompatible for numeric operations
2529 2533
 	 */
2530 2534
 	private static function resolveNumericOperands(CellValue $a, CellValue $b, string $op): array {
2531
-		if ($a->type == self::TYPE_ERROR) throw $a->value;
2532
-		if ($b->type == self::TYPE_ERROR) throw $b->value;
2533
-		if ($a->type == self::TYPE_STRING || $b->type == self::TYPE_STRING) {
2535
+		if ($a->type === self::TYPE_ERROR) throw $a->value;
2536
+		if ($b->type === self::TYPE_ERROR) throw $b->value;
2537
+		if ($a->type === self::TYPE_STRING || $b->type === self::TYPE_STRING) {
2534 2538
 			throw new CellEvaluationException("Cannot perform math on text values");
2535 2539
 		}
2536 2540
 
2537
-		if ($a->type == self::TYPE_BLANK) {
2538
-			if ($b->type == self::TYPE_BLANK) {
2541
+		if ($a->type === self::TYPE_BLANK) {
2542
+			if ($b->type === self::TYPE_BLANK) {
2539 2543
 				return [ 0, 0, self::TYPE_NUMBER, 0 ];
2540 2544
 			}
2541 2545
 			return [ 0, $b->value, $b->type ];
2542
-		} elseif ($b->type == self::TYPE_BLANK) {
2546
+		} elseif ($b->type === self::TYPE_BLANK) {
2543 2547
 			return [ $a->value, 0, $a->type ];
2544 2548
 		}
2545 2549
 
2546
-		$isMultOrDiv = ($op == '*' || $op == '/' || $op == '%');
2550
+		$isMultOrDiv = ($op === '*' || $op === '/' || $op === '%');
2547 2551
 
2548
-		if ($a->type == $b->type) {
2552
+		if ($a->type === $b->type) {
2549 2553
 			return [ $a->value, $b->value, $a->type ];
2550 2554
 		}
2551 2555
 
@@ -2604,10 +2608,10 @@ class CellValue {
2604 2608
 	 * @throws CellException  if `$a` or `$b` is of type `TYPE_ERROR`
2605 2609
 	 */
2606 2610
 	private static function resolveComparableArguments(CellValue $a, CellValue $b): array {
2607
-		if ($a->type == CellValue::TYPE_ERROR) throw $a->value;
2608
-		if ($b->type == CellValue::TYPE_ERROR) throw $b->value;
2609
-		if ($a->type == CellValue::TYPE_FORMULA) throw new CellEvaluationException("Can't compare formula values");
2610
-		if ($b->type == CellValue::TYPE_FORMULA) throw new CellEvaluationException("Can't compare formula values");
2611
+		if ($a->type === CellValue::TYPE_ERROR) throw $a->value;
2612
+		if ($b->type === CellValue::TYPE_ERROR) throw $b->value;
2613
+		if ($a->type === CellValue::TYPE_FORMULA) throw new CellEvaluationException("Can't compare formula values");
2614
+		if ($b->type === CellValue::TYPE_FORMULA) throw new CellEvaluationException("Can't compare formula values");
2611 2615
 		$aNumValue = $a->value;
2612 2616
 		$bNumValue = $b->value;
2613 2617
 		$aStrValue = "{$a->value}";
@@ -2630,7 +2634,7 @@ class CellValue {
2630 2634
 				$bNumValue = ($bNumValue) ? 1 : 0;
2631 2635
 				break;
2632 2636
 		}
2633
-		if ($a->type == CellValue::TYPE_STRING || $b->type == CellValue::TYPE_STRING) {
2637
+		if ($a->type === CellValue::TYPE_STRING || $b->type === CellValue::TYPE_STRING) {
2634 2638
 			return [ $aStrValue, $bStrValue ];
2635 2639
 		}
2636 2640
 		return [ $aNumValue, $bNumValue ];
@@ -2691,10 +2695,10 @@ class CellValue {
2691 2695
 	 * Tests if a type is numeric.
2692 2696
 	 */
2693 2697
 	public static function isTypeNumeric(string $type): bool {
2694
-		return $type == CellValue::TYPE_NUMBER ||
2695
-			$type == CellValue::TYPE_PERCENT ||
2696
-			$type == CellValue::TYPE_CURRENCY ||
2697
-			$type == CellValue::TYPE_BOOLEAN;
2698
+		return $type === CellValue::TYPE_NUMBER ||
2699
+			$type === CellValue::TYPE_PERCENT ||
2700
+			$type === CellValue::TYPE_CURRENCY ||
2701
+			$type === CellValue::TYPE_BOOLEAN;
2698 2702
 	}
2699 2703
 
2700 2704
 	public function __toString(): string {
@@ -2736,9 +2740,9 @@ class SpreadsheetGrid {
2736 2740
 	 */
2737 2741
 	public function cellAt(CellAddress $address): SpreadsheetCell {
2738 2742
 		$c = $address->columnIndex; $r = $address->rowIndex;
2739
-		if ($c < 0 || $c >= sizeof($this->cells)) throw new CellEvaluationException("Unresolved cell address {$address->name}", '#REF');
2743
+		if ($c < 0 || $c >= count($this->cells)) throw new CellEvaluationException("Unresolved cell address {$address->name}", '#REF');
2740 2744
 		$col = $this->cells[$c];
2741
-		if ($r < 0 || $r >= sizeof($col)) throw new CellEvaluationException("Unresolved cell address {$address->name}", '#REF');
2745
+		if ($r < 0 || $r >= count($col)) throw new CellEvaluationException("Unresolved cell address {$address->name}", '#REF');
2742 2746
 		return $col[$r];
2743 2747
 	}
2744 2748
 
@@ -2796,17 +2800,19 @@ class MDSpreadsheetReader extends MDReader {
2796 2800
 
2797 2801
 	private function processTable(MDTableNode $tableNode, MDState $state) {
2798 2802
 		// Measure table
2799
-		$rowCount = sizeof($tableNode->bodyRows());
2803
+		$rowCount = count($tableNode->bodyRows());
2800 2804
 		$columnCount = 0;
2801 2805
 		foreach ($tableNode->bodyRows() as $row) {
2802
-			$columnCount = max($columnCount, sizeof($row->children));
2806
+			$columnCount = max($columnCount, count($row->children));
2803 2807
 		}
2804 2808
 
2805 2809
 		// Create and populate grid
2806 2810
 		$grid = new SpreadsheetGrid($columnCount, $rowCount);
2807 2811
 		for ($c = 0; $c < $columnCount; $c++) {
2808 2812
 			for ($r = 0; $r < $rowCount; $r++) {
2809
-				$cellNode = $tableNode->bodyRows()[$r]->children[$c];
2813
+				$row = $tableNode->bodyRows()[$r] ?? null;
2814
+				if ($row === null) continue;
2815
+				$cellNode = $row->children[$c] ?? null;
2810 2816
 				if ($cellNode === null) continue;
2811 2817
 				$cellText = $cellNode->toPlaintext($state);
2812 2818
 				$gridCell = $grid->cells[$c][$r];
@@ -2833,7 +2839,9 @@ class MDSpreadsheetReader extends MDReader {
2833 2839
 		// Copy results back to table
2834 2840
 		for ($c = 0; $c < $columnCount; $c++) {
2835 2841
 			for ($r = 0; $r < $rowCount; $r++) {
2836
-				$cellNode = $tableNode->bodyRows()[$r]->children[$c];
2842
+				$row = $tableNode->bodyRows()[$r] ?? null;
2843
+				if ($row === null) continue;
2844
+				$cellNode = $row->children[$c] ?? null;
2837 2845
 				if ($cellNode === null) continue;
2838 2846
 				$gridCell = $grid->cells[$c][$r];
2839 2847
 				$gridValue = $gridCell->outputValue;
@@ -2843,7 +2851,7 @@ class MDSpreadsheetReader extends MDReader {
2843 2851
 					$cellNode->addClass('calculated');
2844 2852
 				}
2845 2853
 				$cellNode->addClass("spreadsheet-type-{$gridValue->type}");
2846
-				if ($gridValue->type == CellValue::TYPE_ERROR) {
2854
+				if ($gridValue->type === CellValue::TYPE_ERROR) {
2847 2855
 					$cellNode->attributes['title'] = $gridValue->value;
2848 2856
 				}
2849 2857
 				$gridNumber = $gridValue->numericValue();

+ 6
- 5
playground.html Просмотреть файл

@@ -403,11 +403,12 @@ Heading, underline style
403 403
 
404 404
 ### Modified Heading {style=color:red;}
405 405
 
406
-| Unit Price | Qty | Subtotal |
407
-| ---: | ---: | ---: |
408
-| $1.23 | 2 | =A*B FILL |
409
-| $4.99 | 6 | |
410
-| Total | | =SUM(C:C) |
406
+| Unit Price | Qty | Discount | Subtotal |
407
+| ---: | ---: | ---: | ---: |
408
+| $1.23 | 2 | 10% | =A\\*B*(1-C) FILL |
409
+| $4.99 | 6 | | |
410
+| $0.99 | 1 | 25% | |
411
+| Total | | | =SUM(D:D) |
411 412
 
412 413
 ---
413 414
 

Загрузка…
Отмена
Сохранить