Преглед изворни кода

Spreadsheet PHP mostly working

main
Rocketsoup пре 1 година
родитељ
комит
0e986f8081
4 измењених фајлова са 202 додато и 187 уклоњено
  1. 1
    1
      js/spreadsheet.js
  2. 1
    1
      js/spreadsheet.min.js
  3. 178
    175
      php/spreadsheet.php
  4. 22
    10
      playgroundapi.php

+ 1
- 1
js/spreadsheet.js Прегледај датотеку

@@ -1933,7 +1933,7 @@ class CellAddress {
1933 1933
 		if (!this.isResolved && resolveToRow) {
1934 1934
 			newRowIndex = relativeFrom.rowIndex;
1935 1935
 		}
1936
-		if (newRowIndex != -1 && !this.isRowAbsolute) {
1936
+		if (newRowIndex != -1 && !this.isRowFixed) {
1937 1937
 			const rowDelta = relativeTo.rowIndex - relativeFrom.rowIndex;
1938 1938
 			newRowIndex += rowDelta;
1939 1939
 		}

+ 1
- 1
js/spreadsheet.min.js
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 178
- 175
php/spreadsheet.php Прегледај датотеку

@@ -85,11 +85,11 @@ enum CellExpressionTokenType {
85 85
  * `Function`.
86 86
  */
87 87
 enum CellExpressionOperation {
88
-	/** Arg is `number` */
88
+	/** Arg is `float` */
89 89
 	case Number;
90 90
 	/** Arg is `string` without quotes */
91 91
 	case String;
92
-	/** Arg is `boolean` */
92
+	/** Arg is `bool` */
93 93
 	case Boolean;
94 94
 	/** Arg is `CellAddress` */
95 95
 	case Reference;
@@ -154,8 +154,11 @@ class CellExpressionSet {
154 154
 		/** @$CellAddress[] */
155 155
 		$expressionAddressQueue = [];
156 156
 		$range = new CellAddressRange(new CellAddress(0, 0), new CellAddress($colCount - 1, $rowCount - 1));
157
-		foreach ($range->cellsIn($this->grid) as $address) {
158
-			$cell = $this->grid->cellAt($address);
157
+		foreach ($range->cellsIn($this->grid) as $addressStr => $cell) {
158
+			$address = CellAddress::fromString($addressStr);
159
+			if ($address === null) {
160
+				error_log("Couldn't parse address string {$addressStr}!");
161
+			}
159 162
 			$value = $cell->originalValue;
160 163
 			if ($value->type != CellValue::TYPE_FORMULA) {
161 164
 				$cell->outputValue = $value;
@@ -235,8 +238,8 @@ class CellExpressionSet {
235 238
 	 */
236 239
 	private function enqueueFilledBlanks(CellExpression $expression, array &$addresses) {
237 240
 		foreach ($expression->fillRanges ?? [] as $range) {
238
-			foreach ($range->cellsIn($this->grid) as $filledAddress) {
239
-				$filledCell = $this->grid->cellAt($filledAddress);
241
+			foreach ($range->cellsIn($this->grid) as $filledAddressStr => $filledCell) {
242
+				$filledAddress = CellAddress::fromString($filledAddressStr);
240 243
 				if ($filledCell->originalValue->type === CellValue::TYPE_BLANK &&
241 244
 						(!$filledCell->outputValue ||
242 245
 						$filledCell->outputValue->type === CellValue::TYPE_BLANK)) {
@@ -309,8 +312,8 @@ class CellExpressionSet {
309 312
 			case CellExpressionOperation::Range: {
310 313
 				$range = $expr->arguments[0];
311 314
 				$values = [];
312
-				foreach ($range->cellsIn($this->grid) as $rAddress) {
313
-					$cell = $this->grid->cellAt($rAddress);
315
+				foreach ($range->cellsIn($this->grid) as $rAddressStr => $cell) {
316
+					$rAddress = CellAddress::fromString($rAddressStr);
314 317
 					if ($rAddress->equals($address)) continue;
315 318
 					$val = $this->grid->outputValueAt($rAddress);
316 319
 					if ($val === null) {
@@ -653,7 +656,7 @@ class CellExpressionSet {
653 656
 		}
654 657
 		$evaled = array_map(fn($arg) => $this->evaluate($arg, $address), $args);
655 658
 		for ($i = 0; $i < sizeof($evaled) - 1; $i += 2) {
656
-			$test = $evaled[i].booleanValue();
659
+			$test = $evaled[$i].booleanValue();
657 660
 			if ($test === null) {
658 661
 				throw new CellEvaluationException("IFS expects a boolean for argument " . ($i + 1));
659 662
 			}
@@ -964,7 +967,7 @@ class CellExpression {
964 967
 	public ?string $qualifier;
965 968
 
966 969
 	/**
967
-	 * Optional format override. One of `number`, `currency`, `percent`.
970
+	 * Optional format override. One of `"number"`, `"currency"`, `"percent"`.
968 971
 	 */
969 972
 	public ?string $outputType = null;
970 973
 
@@ -1005,9 +1008,9 @@ class CellExpression {
1005 1008
 	 * @return ?CellExpression  parsed expression, or `null` if it failed
1006 1009
 	 */
1007 1010
 	public static function parse(string $expression, CellAddress $address): ?CellExpression {
1008
-		$tokens = $this->expressionToTokens($expression);
1011
+		$tokens = self::expressionToTokens($expression);
1009 1012
 		if (sizeof($tokens) == 0) return null;
1010
-		$expr = $this->expressionFromTokens($tokens, $address);
1013
+		$expr = self::expressionFromTokens($tokens, $address);
1011 1014
 		$expr->location = $address;
1012 1015
 		return $expr;
1013 1016
 	}
@@ -1047,10 +1050,7 @@ class CellExpression {
1047 1050
 	}
1048 1051
 
1049 1052
 	private function clone(): CellExpression {
1050
-		$cp = new CellExpression();
1051
-		$cp->op = $this->op;
1052
-		$cp->arguments = array($this->arguments);
1053
-		$cp->qualifier = $this->qualifier;
1053
+		$cp = new CellExpression($this->op, array($this->arguments), $this->qualifier);
1054 1054
 		$cp->outputType = $this->outputType;
1055 1055
 		$cp->outputDecimals = $this->outputDecimals;
1056 1056
 		$cp->fillRanges = $this->fillRanges !== null ? array($this->fillRanges) : null;
@@ -1089,17 +1089,17 @@ class CellExpression {
1089 1089
 	 */
1090 1090
 	public static function expressionToTokens(string $text): array {
1091 1091
 		$tokens = [];
1092
-		$pos = [0];
1093
-		$this->skipWhitespace($text, $pos);
1094
-		if (mb_substr($text, $pos[0], 1) == '=') {
1092
+		$pos = 0;
1093
+		self::skipWhitespace($text, $pos);
1094
+		if (mb_substr($text, $pos, 1) === '=') {
1095 1095
 			// Ignore equals
1096
-			$pos[0]++;
1096
+			$pos++;
1097 1097
 		}
1098
-		$this->skipWhitespace($text, $pos);
1098
+		self::skipWhitespace($text, $pos);
1099 1099
 		$l = mb_strlen($text);
1100
-		while ($pos[0] < $l) {
1101
-			array_push($tokens, $this->readNextToken($text, $pos));
1102
-			$this->skipWhitespace($text, $pos);
1100
+		while ($pos < $l) {
1101
+			array_push($tokens, self::readNextToken($text, $pos));
1102
+			self::skipWhitespace($text, $pos);
1103 1103
 		}
1104 1104
 		return $tokens;
1105 1105
 	}
@@ -1111,28 +1111,28 @@ class CellExpression {
1111 1111
 	 */
1112 1112
 	private static function readNextToken(string $text, int &$pos): CellExpressionToken {
1113 1113
 		// Single char tokens
1114
-		if ($token = $this->readNextSimpleToken($text, $pos, '==', CellExpressionTokenType::Equal)) return $token;
1115
-		if ($token = $this->readNextSimpleToken($text, $pos, '!=', CellExpressionTokenType::Unequal)) return $token;
1116
-		if ($token = $this->readNextSimpleToken($text, $pos, '<=', CellExpressionTokenType::LessThanEqual)) return $token;
1117
-		if ($token = $this->readNextSimpleToken($text, $pos, '>=', CellExpressionTokenType::GreaterThanEqual)) return $token;
1118
-		if ($token = $this->readNextSimpleToken($text, $pos, '<', CellExpressionTokenType::LessThan)) return $token;
1119
-		if ($token = $this->readNextSimpleToken($text, $pos, '>', CellExpressionTokenType::GreaterThan)) return $token;
1120
-		if ($token = $this->readNextSimpleToken($text, $pos, '!', CellExpressionTokenType::Not)) return $token;
1121
-		if ($token = $this->readNextSimpleToken($text, $pos, '+', CellExpressionTokenType::Plus)) return $token;
1122
-		if ($token = $this->readNextSimpleToken($text, $pos, '-', CellExpressionTokenType::Minus)) return $token;
1123
-		if ($token = $this->readNextSimpleToken($text, $pos, '*', CellExpressionTokenType::Multiply)) return $token;
1124
-		if ($token = $this->readNextSimpleToken($text, $pos, '/', CellExpressionTokenType::Divide)) return $token;
1125
-		if ($token = $this->readNextSimpleToken($text, $pos, ',', CellExpressionTokenType::Comma)) return $token;
1126
-		if ($token = $this->readNextSimpleToken($text, $pos, '(', CellExpressionTokenType::OpenParen)) return $token;
1127
-		if ($token = $this->readNextSimpleToken($text, $pos, ')', CellExpressionTokenType::CloseParen)) return $token;
1128
-		if ($token = $this->readNextSimpleToken($text, $pos, ':', CellExpressionTokenType::Colon)) return $token;
1129
-		if ($token = $this->readNextSimpleToken($text, $pos, ';', CellExpressionTokenType::Semicolon)) return $token;
1130
-		if ($token = $this->readNextSimpleToken($text, $pos, '&', CellExpressionTokenType::Ampersand)) return $token;
1114
+		if ($token = self::readNextSimpleToken($text, $pos, '==', CellExpressionTokenType::Equal)) return $token;
1115
+		if ($token = self::readNextSimpleToken($text, $pos, '!=', CellExpressionTokenType::Unequal)) return $token;
1116
+		if ($token = self::readNextSimpleToken($text, $pos, '<=', CellExpressionTokenType::LessThanEqual)) return $token;
1117
+		if ($token = self::readNextSimpleToken($text, $pos, '>=', CellExpressionTokenType::GreaterThanEqual)) return $token;
1118
+		if ($token = self::readNextSimpleToken($text, $pos, '<', CellExpressionTokenType::LessThan)) return $token;
1119
+		if ($token = self::readNextSimpleToken($text, $pos, '>', CellExpressionTokenType::GreaterThan)) return $token;
1120
+		if ($token = self::readNextSimpleToken($text, $pos, '!', CellExpressionTokenType::Not)) return $token;
1121
+		if ($token = self::readNextSimpleToken($text, $pos, '+', CellExpressionTokenType::Plus)) return $token;
1122
+		if ($token = self::readNextSimpleToken($text, $pos, '-', CellExpressionTokenType::Minus)) return $token;
1123
+		if ($token = self::readNextSimpleToken($text, $pos, '*', CellExpressionTokenType::Multiply)) return $token;
1124
+		if ($token = self::readNextSimpleToken($text, $pos, '/', CellExpressionTokenType::Divide)) return $token;
1125
+		if ($token = self::readNextSimpleToken($text, $pos, ',', CellExpressionTokenType::Comma)) return $token;
1126
+		if ($token = self::readNextSimpleToken($text, $pos, '(', CellExpressionTokenType::OpenParen)) return $token;
1127
+		if ($token = self::readNextSimpleToken($text, $pos, ')', CellExpressionTokenType::CloseParen)) return $token;
1128
+		if ($token = self::readNextSimpleToken($text, $pos, ':', CellExpressionTokenType::Colon)) return $token;
1129
+		if ($token = self::readNextSimpleToken($text, $pos, ';', CellExpressionTokenType::Semicolon)) return $token;
1130
+		if ($token = self::readNextSimpleToken($text, $pos, '&', CellExpressionTokenType::Ampersand)) return $token;
1131 1131
 		// Other tokens
1132
-		if ($token = $this->readNextAddressToken($text, $pos)) return $token;
1133
-		if ($token = $this->readNextNameToken($text, $pos)) return $token;
1134
-		if ($token = $this->readNextNumberToken($text, $pos)) return $token;
1135
-		if ($token = $this->readNextStringToken($text, $pos)) return $token;
1132
+		if ($token = self::readNextAddressToken($text, $pos)) return $token;
1133
+		if ($token = self::readNextNameToken($text, $pos)) return $token;
1134
+		if ($token = self::readNextNumberToken($text, $pos)) return $token;
1135
+		if ($token = self::readNextStringToken($text, $pos)) return $token;
1136 1136
 		$ch = mb_substr($text, $pos, 1);
1137 1137
 		throw new CellSyntaxException("Unexpected character \"{$ch}\" at {$pos}");
1138 1138
 	}
@@ -1166,25 +1166,25 @@ class CellExpression {
1166 1166
 		$address = '';
1167 1167
 		$isName = true;
1168 1168
 		if ($ch == '$') {
1169
-			$address += $ch;
1169
+			$address .= $ch;
1170 1170
 			$isName = false;
1171 1171
 			$p++;
1172 1172
 		}
1173
-		$col = $this->readChars($text, $p, fn($s) => $this->isLetter($s), 1, 2);
1173
+		$col = self::readChars($text, $p, fn($s) => self::isLetter($s), 1, 2);
1174 1174
 		if ($col === null) return null;
1175
-		$address += $col;
1175
+		$address .= $col;
1176 1176
 		$ch = mb_substr($text, $p, 1);
1177 1177
 		if ($ch == '$') {
1178
-			$address += $ch;
1178
+			$address .= $ch;
1179 1179
 			$isName = false;
1180 1180
 			$p++;
1181
-			$row = $this->readChars($text, $p, $this->isDigit, 1);
1181
+			$row = self::readChars($text, $p, fn($ch) => self::isDigit($ch), 1);
1182 1182
 			if ($row === null) return null;
1183
-			$address += $row;
1183
+			$address .= $row;
1184 1184
 		} else {
1185
-			$row = $this->readChars($text, $p, $this->isDigit, 0);
1185
+			$row = self::readChars($text, $p, fn($ch) => self::isDigit($ch), 0);
1186 1186
 			if ($row === null) return null;
1187
-			$address += $row;
1187
+			$address .= $row;
1188 1188
 		}
1189 1189
 		$pos = $p;
1190 1190
 		return new CellExpressionToken(
@@ -1194,7 +1194,7 @@ class CellExpression {
1194 1194
 
1195 1195
 	private static function readNextNameToken(string $text, int &$pos): ?CellExpressionToken {
1196 1196
 		$p = $pos;
1197
-		$name = $this->readChars($text, $p, fn($s) => $this->isLetter($s), 1);
1197
+		$name = self::readChars($text, $p, fn($s) => self::isLetter($s), 1);
1198 1198
 		if ($name === null) return null;
1199 1199
 		$pos = $p;
1200 1200
 		if (CellAddress::isAddress($name)) {
@@ -1205,13 +1205,13 @@ class CellExpression {
1205 1205
 
1206 1206
 	private static function readNextNumberToken(string $text, int &$pos): ?CellExpressionToken {
1207 1207
 		$ch = mb_substr($text, $pos, 1);
1208
-		if (!$this->isDigit($ch)) return null;
1208
+		if (!self::isDigit($ch)) return null;
1209 1209
 		$l = mb_strlen($text);
1210 1210
 		$numStr = $ch;
1211 1211
 		$pos++;
1212 1212
 		while ($pos < $l) {
1213 1213
 			$ch = mb_substr($text, $pos, 1);
1214
-			if ($this->isDigit($ch)) {
1214
+			if (self::isDigit($ch)) {
1215 1215
 				$pos++;
1216 1216
 				$numStr .= $ch;
1217 1217
 			} else {
@@ -1222,10 +1222,10 @@ class CellExpression {
1222 1222
 			$ch = mb_substr($text, $pos, 1);
1223 1223
 			if ($ch == '.') {
1224 1224
 				$numStr .= $ch;
1225
-				$pos[0]++;
1225
+				$pos++;
1226 1226
 				while ($pos < $l) {
1227
-					$ch = mb_substr($text, $pos[0], 1);
1228
-					if ($this->isDigit($ch)) {
1227
+					$ch = mb_substr($text, $pos, 1);
1228
+					if (self::isDigit($ch)) {
1229 1229
 						$pos++;
1230 1230
 						$numStr .= $ch;
1231 1231
 					} else {
@@ -1245,7 +1245,7 @@ class CellExpression {
1245 1245
 		$l = mb_strlen($text);
1246 1246
 		$inEscape = false;
1247 1247
 		while ($pos < $l) {
1248
-			$ch = mb_substr($text, $pos[0], $pos[0] + 1);
1248
+			$ch = mb_substr($text, $pos, 1);
1249 1249
 			$pos++;
1250 1250
 			if ($inEscape) {
1251 1251
 				$inEscape = false;
@@ -1259,7 +1259,7 @@ class CellExpression {
1259 1259
 			} elseif ($ch == '"') {
1260 1260
 				return new CellExpressionToken(CellExpressionTokenType::String, $str);
1261 1261
 			} else {
1262
-				$str += $ch;
1262
+				$str .= $ch;
1263 1263
 			}
1264 1264
 		}
1265 1265
 		throw new CellSyntaxException('Unterminated string');
@@ -1284,11 +1284,11 @@ class CellExpression {
1284 1284
 		while ($p < $l && ($maximumLength === null || $sl < $maximumLength)) {
1285 1285
 			$ch = mb_substr($text, $p, 1);
1286 1286
 			if (!$charTest($ch)) break;
1287
-			$s += $ch;
1287
+			$s .= $ch;
1288 1288
 			$sl++;
1289 1289
 			$p++;
1290 1290
 		}
1291
-		if ($p < $l && $charTest(mb_substr($text, $p, $p + 1))) {
1291
+		if ($p < $l && $charTest(mb_substr($text, $p, 1))) {
1292 1292
 			return null;
1293 1293
 		}
1294 1294
 		if ($minimumLength !== null && $sl < $minimumLength) {
@@ -1324,9 +1324,9 @@ class CellExpression {
1324 1324
 	 * @return ?CellExpression
1325 1325
 	 */
1326 1326
 	public static function expressionFromTokens(array $tokens, CellAddress $address): ?CellExpression {
1327
-		if ($expr = $this->tryExpressionAndFormat($tokens, 0, sizeof($tokens) - 1, $address)) return $expr;
1328
-		if ($expr = $this->tryExpressionAndFill($tokens, 0, sizeof($tokens) - 1, $address)) return $expr;
1329
-		if ($expr = $this->tryExpression($tokens, 0, sizeof($tokens) - 1, $address)) return $expr;
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;
1330 1330
 		return null;
1331 1331
 	}
1332 1332
 
@@ -1340,11 +1340,11 @@ class CellExpression {
1340 1340
 	private static function tryExpressionAndFormat(array $tokens, int $start,
1341 1341
 			int $end, CellAddress $address): ?CellExpression {
1342 1342
 		for ($t = $start + 1; $t < $end; $t++) {
1343
-			if ($tokens[t]->type == CellExpressionTokenType::Semicolon) {
1344
-				$expr = $this->tryExpressionAndFill($tokens, $start, $t - 1, $address) ??
1345
-					$this->tryExpression($tokens, $start, $t - 1, $address);
1343
+			if ($tokens[$t]->type == CellExpressionTokenType::Semicolon) {
1344
+				$expr = self::tryExpressionAndFill($tokens, $start, $t - 1, $address) ??
1345
+					self::tryExpression($tokens, $start, $t - 1, $address);
1346 1346
 				if ($expr === null) return null;
1347
-				$format = $this->tryFormat($tokens, $t + 1, $end, $address);
1347
+				$format = self::tryFormat($tokens, $t + 1, $end, $address);
1348 1348
 				if ($format === null) return null;
1349 1349
 				[ $expr->outputType, $expr->outputDecimals ] = $format;
1350 1350
 				return $expr;
@@ -1367,7 +1367,7 @@ class CellExpression {
1367 1367
 		if (!$tokens[$end]->type->isPotentialName()) return null;
1368 1368
 		$name = mb_strtoupper($tokens[$end]->content);
1369 1369
 		if ($name != 'FILL') return null;
1370
-		$exp = $this->tryExpression($tokens, $start, $end - 1, $address);
1370
+		$exp = self::tryExpression($tokens, $start, $end - 1, $address);
1371 1371
 		$columnIndex = $address->columnIndex;
1372 1372
 		$exp->fillRanges = [
1373 1373
 			new CellAddressRange(new CellAddress($columnIndex, -1), new CellAddress($columnIndex, -1)),
@@ -1416,22 +1416,22 @@ class CellExpression {
1416 1416
 	 */
1417 1417
 	private static function tryExpression(array $tokens, int $start, int $end,
1418 1418
 			CellAddress $address): CellExpression {
1419
-		if ($expr = $this->tryParenExpression($tokens, $start, $end, $address)) return $expr;
1420
-		if ($expr = $this->tryNumber($tokens, $start, $end, $address)) return $expr;
1421
-		if ($expr = $this->tryString($tokens, $start, $end, $address)) return $expr;
1422
-		if ($expr = $this->tryBoolean($tokens, $start, $end, $address)) return $expr;
1423
-		if ($expr = $this->tryFunction($tokens, $start, $end, $address)) return $expr;
1424
-		if ($expr = $this->tryRange($tokens, $start, $end, $address)) return $expr;
1425
-		if ($expr = $this->tryReference($tokens, $start, $end, $address)) return $expr;
1426
-		if ($expr = $this->tryInfix($tokens, $start, $end, $address)) return $expr;
1427
-		if ($expr = $this->tryUnary($tokens, $start, $end, $address)) return $expr;
1419
+		if ($expr = self::tryParenExpression($tokens, $start, $end, $address)) return $expr;
1420
+		if ($expr = self::tryNumber($tokens, $start, $end, $address)) return $expr;
1421
+		if ($expr = self::tryString($tokens, $start, $end, $address)) return $expr;
1422
+		if ($expr = self::tryBoolean($tokens, $start, $end, $address)) return $expr;
1423
+		if ($expr = self::tryFunction($tokens, $start, $end, $address)) return $expr;
1424
+		if ($expr = self::tryRange($tokens, $start, $end, $address)) return $expr;
1425
+		if ($expr = self::tryReference($tokens, $start, $end, $address)) return $expr;
1426
+		if ($expr = self::tryInfix($tokens, $start, $end, $address)) return $expr;
1427
+		if ($expr = self::tryUnary($tokens, $start, $end, $address)) return $expr;
1428 1428
 		throw new CellSyntaxException("Invalid expression");
1429 1429
 	}
1430 1430
 
1431 1431
 	/**
1432 1432
 	 * @param CellExpressionToken[] $tokens
1433
-	 * @param number $start
1434
-	 * @param number $end
1433
+	 * @param int $start
1434
+	 * @param int $end
1435 1435
 	 * @param CellAddress $address
1436 1436
 	 * @return CellExpression|null
1437 1437
 	 */
@@ -1449,13 +1449,13 @@ class CellExpression {
1449 1449
 			if ($parenLevel < 0) return null;
1450 1450
 		}
1451 1451
 		if ($parenLevel != 0) return null;
1452
-		return $this->tryExpression($tokens, $start + 1, $end - 1, $address);
1452
+		return self::tryExpression($tokens, $start + 1, $end - 1, $address);
1453 1453
 	}
1454 1454
 
1455 1455
 	/**
1456 1456
 	 * @param CellExpressionToken[] $tokens
1457
-	 * @param number $start
1458
-	 * @param number $end
1457
+	 * @param int $start
1458
+	 * @param int $end
1459 1459
 	 * @param CellAddress $address
1460 1460
 	 * @return CellExpression|null
1461 1461
 	 */
@@ -1473,8 +1473,8 @@ class CellExpression {
1473 1473
 
1474 1474
 	/**
1475 1475
 	 * @param CellExpressionToken[] $tokens
1476
-	 * @param number $start
1477
-	 * @param number $end
1476
+	 * @param int $start
1477
+	 * @param int $end
1478 1478
 	 * @param CellAddress $address
1479 1479
 	 * @return CellExpression|null
1480 1480
 	 */
@@ -1489,8 +1489,8 @@ class CellExpression {
1489 1489
 
1490 1490
 	/**
1491 1491
 	 * @param CellExpressionToken[] $tokens
1492
-	 * @param number $start
1493
-	 * @param number $end
1492
+	 * @param int $start
1493
+	 * @param int $end
1494 1494
 	 * @param CellAddress $address
1495 1495
 	 * @return CellExpression|null
1496 1496
 	 */
@@ -1506,8 +1506,8 @@ class CellExpression {
1506 1506
 
1507 1507
 	/**
1508 1508
 	 * @param CellExpressionToken[] $tokens
1509
-	 * @param number $start
1510
-	 * @param number $end
1509
+	 * @param int $start
1510
+	 * @param int $end
1511 1511
 	 * @param CellAddress $address
1512 1512
 	 * @return CellExpression|null
1513 1513
 	 */
@@ -1519,20 +1519,20 @@ class CellExpression {
1519 1519
 		$qualifier = $tokens[$start]->content;
1520 1520
 		if ($tokens[$start + 1]->type != CellExpressionTokenType::OpenParen) return null;
1521 1521
 		if ($tokens[$end]->type != CellExpressionTokenType::CloseParen) return null;
1522
-		$argList = $this->tryArgumentList($tokens, $start + 2, $end - 1, $address);
1522
+		$argList = self::tryArgumentList($tokens, $start + 2, $end - 1, $address);
1523 1523
 		if ($argList === null) return null;
1524 1524
 		return new CellExpression(CellExpressionOperation::Function, $argList, $qualifier);
1525 1525
 	}
1526 1526
 
1527 1527
 	/**
1528 1528
 	 * @param CellExpressionToken[] $tokens
1529
-	 * @param number $start
1530
-	 * @param number $end
1529
+	 * @param int $start
1530
+	 * @param int $end
1531 1531
 	 * @param CellAddress $address
1532 1532
 	 * @return CellExpression[]|null
1533 1533
 	 */
1534 1534
 	private static function tryArgumentList(array $tokens, int $start, int $end,
1535
-			CellAddress $address): ?CellExpression {
1535
+			CellAddress $address): ?array {
1536 1536
 		$count = $end - $start + 1;
1537 1537
 		if ($count == 0) return [];
1538 1538
 		$parenDepth = 0;
@@ -1558,17 +1558,17 @@ class CellExpression {
1558 1558
 		// Convert token ranges to expressions
1559 1559
 		$args = [];
1560 1560
 		foreach ($argTokens as $argToken) {
1561
-			$arg = $this->tryExpression($tokens, $argToken[0], $argToken[1], $address);
1561
+			$arg = self::tryExpression($tokens, $argToken[0], $argToken[1], $address);
1562 1562
 			if ($arg === null) return null;
1563
-			$args.push($arg);
1563
+			array_push($args, $arg);
1564 1564
 		}
1565 1565
 		return $args;
1566 1566
 	}
1567 1567
 
1568 1568
 	/**
1569 1569
 	 * @param CellExpressionToken[] $tokens
1570
-	 * @param number $start
1571
-	 * @param number $end
1570
+	 * @param int $start
1571
+	 * @param int $end
1572 1572
 	 * @param CellAddress $address
1573 1573
 	 * @return CellExpression|null
1574 1574
 	 */
@@ -1589,8 +1589,8 @@ class CellExpression {
1589 1589
 
1590 1590
 	/**
1591 1591
 	 * @param CellExpressionToken[] $tokens
1592
-	 * @param number $start
1593
-	 * @param number $end
1592
+	 * @param int $start
1593
+	 * @param int $end
1594 1594
 	 * @param CellAddress $address
1595 1595
 	 * @return CellExpression|null
1596 1596
 	 */
@@ -1619,8 +1619,8 @@ class CellExpression {
1619 1619
 
1620 1620
 	/**
1621 1621
 	 * @param CellExpressionToken[] $tokens
1622
-	 * @param number $start
1623
-	 * @param number $end
1622
+	 * @param int $start
1623
+	 * @param int $end
1624 1624
 	 * @param CellAddress $address
1625 1625
 	 * @return CellExpression|null
1626 1626
 	 */
@@ -1637,9 +1637,9 @@ class CellExpression {
1637 1637
 				$parenLevel--;
1638 1638
 			} elseif ($parenLevel == 0 && $i > $start && $i < $end) {
1639 1639
 				$op = $tokens[$i]->type->name;
1640
-				$priority = $this->infixPriority[$op] ?? false;
1640
+				$priority = self::infixPriority[$op] ?? false;
1641 1641
 				if ($priority === false) continue;
1642
-				array_push($candidates, [ 'priority' => priority, 'i' => $i ]);
1642
+				array_push($candidates, [ 'priority' => $priority, 'i' => $i ]);
1643 1643
 			}
1644 1644
 		}
1645 1645
 		usort($candidates, fn($a, $b) => $a['priority'] - $b['priority']);
@@ -1647,9 +1647,9 @@ class CellExpression {
1647 1647
 		foreach ($candidates as $candidate) {
1648 1648
 			try {
1649 1649
 				$i = $candidate['i'];
1650
-				$operand1 = $this->tryExpression($tokens, $start, $i - 1, $address);
1650
+				$operand1 = self::tryExpression($tokens, $start, $i - 1, $address);
1651 1651
 				if ($operand1 === null) continue;
1652
-				$operand2 = $this->tryExpression($tokens, $i + 1, $end, $address);
1652
+				$operand2 = self::tryExpression($tokens, $i + 1, $end, $address);
1653 1653
 				if ($operand2 === null) continue;
1654 1654
 				$bestCandidate = $candidate;
1655 1655
 				break;
@@ -1662,7 +1662,7 @@ class CellExpression {
1662 1662
 		if ($bestCandidate === null) {
1663 1663
 			return null;
1664 1664
 		}
1665
-		$i = $bestCandidate->i;
1665
+		$i = $bestCandidate['i'];
1666 1666
 		switch ($tokens[$bestCandidate['i']]->type) {
1667 1667
 			case CellExpressionTokenType::Plus:
1668 1668
 				return new CellExpression(CellExpressionOperation::Add, [ $operand1, $operand2 ]);
@@ -1692,8 +1692,8 @@ class CellExpression {
1692 1692
 
1693 1693
 	/**
1694 1694
 	 * @param CellExpressionToken[] $tokens
1695
-	 * @param number $start
1696
-	 * @param number $end
1695
+	 * @param int $start
1696
+	 * @param int $end
1697 1697
 	 * @param CellAddress $address
1698 1698
 	 * @return CellExpression|null
1699 1699
 	 */
@@ -1707,7 +1707,7 @@ class CellExpression {
1707 1707
 		];
1708 1708
 		foreach ($ops as $op) {
1709 1709
 			if ($tokens[$start]->type != $op[0]) continue;
1710
-			$operand = $this->tryExpression($tokens, $start + 1, $end, $address);
1710
+			$operand = self::tryExpression($tokens, $start + 1, $end, $address);
1711 1711
 			if ($operand === null) return null;
1712 1712
 			return new CellExpression($op[1], [ $operand ]);
1713 1713
 		}
@@ -1744,12 +1744,12 @@ class CellAddress {
1744 1744
 	 * Whether the column should remain unchanged when transposed. This is
1745 1745
 	 * symbolized by prefixing the column name with a `$` (e.g. `$C3`).
1746 1746
 	 */
1747
-	public bool $isColumnFixed = false;
1747
+	public bool $isColumnFixed;
1748 1748
 
1749 1749
 	/**
1750 1750
 	 * Zero-based column index.
1751 1751
 	 */
1752
-	public int $columnIndex = -1;
1752
+	public int $columnIndex;
1753 1753
 
1754 1754
 	/**
1755 1755
 	 * Letter code for the column.
@@ -1760,12 +1760,12 @@ class CellAddress {
1760 1760
 	 * Whether the row should remain unchanged when transposed. This is
1761 1761
 	 * symbolized by prefixing the row number with a `$` (e.g. `C$3`).
1762 1762
 	 */
1763
-	public bool $isRowFixed = false;
1763
+	public bool $isRowFixed;
1764 1764
 
1765 1765
 	/**
1766 1766
 	 * Zero-based row index.
1767 1767
 	 */
1768
-	public int $rowIndex = -1;
1768
+	public int $rowIndex;
1769 1769
 
1770 1770
 	/**
1771 1771
 	 * One-based row number. This is the human-facing row number.
@@ -1775,7 +1775,7 @@ class CellAddress {
1775 1775
 	/**
1776 1776
 	 * Whether this address has both a definite column and row.
1777 1777
 	 */
1778
-	public function isResolved(): bool { return $this->columnIndex >= 0 && $this->rowIndex >= 0; }
1778
+	public bool $isResolved;
1779 1779
 
1780 1780
 	/**
1781 1781
 	 * @param int $columnIndex  0-based column index
@@ -1797,6 +1797,7 @@ class CellAddress {
1797 1797
 		$this->rowIndex = $rowIndex;
1798 1798
 		$this->isColumnFixed = $isColumnFixed;
1799 1799
 		$this->isRowFixed = $isRowFixed;
1800
+		$this->isResolved = ($columnIndex >= 0 && $rowIndex >= 0);
1800 1801
 		$this->name = self::formatAddress($columnIndex, $rowIndex, $isColumnFixed, $isRowFixed);
1801 1802
 	}
1802 1803
 
@@ -1828,13 +1829,13 @@ class CellAddress {
1828 1829
 	 * @param CellAddress $relativeFrom  original address of the formula
1829 1830
 	 * @param CellAddress $relativeTo  address where the formula is being
1830 1831
 	 *   repeated
1831
-	 * @param boolean $resolveToRow  whether to fill in a row number if this
1832
+	 * @param bool $resolveToRow  whether to fill in a row number if this
1832 1833
 	 *   address doesn't have one
1833 1834
 	 * @return CellAddress|null   resolved address, or `null` if out of bounds
1834 1835
 	 */
1835 1836
 	public function transpose(CellAddress $relativeFrom, CellAddress $relativeTo,
1836 1837
 			bool $resolveToRow = true): ?CellAddress {
1837
-		if (!$relativeFrom->isResolved() || !$relativeTo->isResolved()) {
1838
+		if (!$relativeFrom->isResolved || !$relativeTo->isResolved) {
1838 1839
 			throw new CellEvaluationException("Can only transpose to and from resolved addresses");
1839 1840
 		}
1840 1841
 		$newColumnIndex = $this->columnIndex;
@@ -1846,7 +1847,7 @@ class CellAddress {
1846 1847
 		if (!$this->isResolved && $resolveToRow) {
1847 1848
 			$newRowIndex = $relativeFrom->rowIndex;
1848 1849
 		}
1849
-		if ($newRowIndex != -1 && !$this->isRowAbsolute) {
1850
+		if ($newRowIndex != -1 && !$this->isRowFixed) {
1850 1851
 			$rowDelta = $relativeTo->rowIndex - $relativeFrom->rowIndex;
1851 1852
 			$newRowIndex += $rowDelta;
1852 1853
 		}
@@ -1891,7 +1892,7 @@ class CellAddress {
1891 1892
 			$ACodepoint = ord('A');
1892 1893
 			$remaining = $columnIndex;
1893 1894
 			do {
1894
-				$letters = chr(ACodepoint + (remaining % 26)) . $letters;
1895
+				$letters = chr($ACodepoint + ($remaining % 26)) . $letters;
1895 1896
 				$remaining = floor($remaining / 26);
1896 1897
 			} while ($remaining > 0);
1897 1898
 		}
@@ -1904,7 +1905,7 @@ class CellAddress {
1904 1905
 		if ($isColumnFixed && $columnIndex >= 0) $addr .= '$';
1905 1906
 		if ($columnIndex >= 0) $addr .= self::columnIndexToLetters($columnIndex);
1906 1907
 		if ($isRowFixed && $rowIndex >= 0) $addr .= '$';
1907
-		if ($rowIndex >= 0) $addr .= "${rowIndex + 1}";
1908
+		if ($rowIndex >= 0) $addr .= ($rowIndex + 1);
1908 1909
 		return $addr;
1909 1910
 	}
1910 1911
 
@@ -1914,7 +1915,7 @@ class CellAddress {
1914 1915
 	 * @param string $address  cell address string
1915 1916
 	 * @param ?CellAddress $relativeTo  address to resolve relative
1916 1917
 	 *   addresses against
1917
-	 * @param boolean $throwIfInvalid  whether to throw an error if address
1918
+	 * @param bool $throwIfInvalid  whether to throw an error if address
1918 1919
 	 *   is invalid
1919 1920
 	 * @return ?CellAddress  address, if parsable
1920 1921
 	 * @throws CellEvaluationException  if the address is invalid and
@@ -1922,7 +1923,7 @@ class CellAddress {
1922 1923
 	 */
1923 1924
 	public static function fromString(string $address, ?CellAddress $relativeTo=null,
1924 1925
 			bool $throwIfInvalid=false): ?CellAddress {
1925
-		if (!mb_eregi('^(\\$?)([A-Z]{1,2}?)((?:\\$(?=[0-9]))?)([0-9]*)$/', $address, $groups)) {
1926
+		if (!mb_eregi('^(\\$?)([A-Z]{1,2}?)((?:\\$(?=[0-9]))?)([0-9]*)$', $address, $groups)) {
1926 1927
 			if ($throwIfInvalid) throw new CellEvaluationException("Bad address \"{$address}\"", '#REF');
1927 1928
 			return null;
1928 1929
 		}
@@ -1930,7 +1931,7 @@ class CellAddress {
1930 1931
 		$letters = mb_strtoupper($groups[2]);
1931 1932
 		$isRowFixed = ($groups[3] == '$');
1932 1933
 		$numbers = $groups[4];
1933
-		$columnIndex = $this->lettersToColumnIndex($letters);
1934
+		$columnIndex = self::lettersToColumnIndex($letters);
1934 1935
 		$rowIndex = (mb_strlen($numbers) == 0) ? -1 : intval($numbers) - 1;
1935 1936
 		if ($columnIndex < 0 && $relativeTo !== null) $columnIndex = $relativeTo->columnIndex;
1936 1937
 		if ($rowIndex < 0 && $relativeTo !== null) $rowIndex = $relativeTo->rowIndex;
@@ -1957,7 +1958,7 @@ class CellAddressRange {
1957 1958
 	 * @param CellAddress $toCell
1958 1959
 	 */
1959 1960
 	public function __construct(CellAddress $fromCell, CellAddress $toCell) {
1960
-		if ($fromCell->isResolved() != $toCell->isResolved()) {
1961
+		if ($fromCell->isResolved != $toCell->isResolved) {
1961 1962
 			throw new CellEvaluationException("Cannot mix resolved and unresolved cell addresses in range: {$fromCell->name} and {$toCell->name}");
1962 1963
 		}
1963 1964
 		$this->minColumnIndex = min($fromCell->columnIndex, $toCell->columnIndex);
@@ -1971,14 +1972,14 @@ class CellAddressRange {
1971 1972
 	}
1972 1973
 
1973 1974
 	/**
1974
-	 * Creates an iterator for every `CellAddress` in this range within the
1975
+	 * Creates an iterator for every `CellAddress` string in this range within the
1975 1976
 	 * confines of the given grid's dimensions. Iterates each row in the first
1976 1977
 	 * column, then each row in the second, etc. Iteration range is inclusive
1977 1978
 	 * of the min and max extents.
1978 1979
 	 * 
1979 1980
 	 * Example:
1980 1981
 	 * ```
1981
-	 * foreach (range.cellsIn(grid) as $address) {
1982
+	 * foreach (range.cellsIn(grid) as $addressString => $cell) {
1982 1983
 	 *     ...
1983 1984
 	 * }
1984 1985
 	 * ```
@@ -1992,12 +1993,12 @@ class CellAddressRange {
1992 1993
 		$min_row = $this->minRowIndex;
1993 1994
 		$max_row = $this->maxRowIndex;
1994 1995
 		if ($min_row == -1) {
1995
-			$min_row = $grid->getHeaderRowCount();
1996
+			$min_row = 0;
1996 1997
 		}
1997 1998
 		if ($max_row == -1) {
1998
-			$max_row = $grid->getRowCount() - 1;
1999
+			$max_row = $grid->rowCount - 1;
1999 2000
 		}
2000
-		$max_col = min($max_col, $grid->getColumnCount());
2001
+		$max_col = min($max_col, $grid->columnCount);
2001 2002
 		return new class($grid, $min_col, $max_col, $min_row, $max_row) implements Iterator {
2002 2003
 			private SpreadsheetGrid $grid;
2003 2004
 			private int $min_col;
@@ -2023,7 +2024,7 @@ class CellAddressRange {
2023 2024
 				if ($this->col >= $this->min_col && $this->col <= $this->max_col &&
2024 2025
 						$this->row >= $this->min_row && $this->row <= $this->max_row) {
2025 2026
 					$this->address = new CellAddress($this->col, $this->row);
2026
-					$this->cell = $this->grid->getCell($this->address);
2027
+					$this->cell = $this->grid->cellAt($this->address);
2027 2028
 					if (!$this->cell) {
2028 2029
 						error_log("WARNING: Iterator found no cell at {$this->address->name}");
2029 2030
 					}
@@ -2072,36 +2073,36 @@ class CellAddressRange {
2072 2073
  */
2073 2074
 class CellValue {
2074 2075
 	/**
2075
-	 * Blank cell. `value` is `null`.
2076
+	 * Blank cell. `$value` is `null`.
2076 2077
 	 */
2077 2078
 	public const TYPE_BLANK = 'blank';
2078 2079
 	/**
2079
-	 * Currency value. `value` is `number`.
2080
+	 * Currency value. `$value` is `float`.
2080 2081
 	 */
2081 2082
 	public const TYPE_CURRENCY = 'currency';
2082 2083
 	/**
2083
-	 * Regular number value. `value` is `number`.
2084
+	 * Regular number value. `$value` is `float`.
2084 2085
 	 */
2085 2086
 	public const TYPE_NUMBER = 'number';
2086 2087
 	/**
2087
-	 * Percentage. `value` is `number`, represented as a ratio (100% = 1.0).
2088
+	 * Percentage. `$value` is `float`, represented as a ratio (100% = 1.0).
2088 2089
 	 */
2089 2090
 	public const TYPE_PERCENT = 'percent';
2090 2091
 	/**
2091
-	 * Unaltered text value. `value` is `string`.
2092
+	 * Unaltered text value. `$value` is `string`.
2092 2093
 	 */
2093 2094
 	public const TYPE_STRING = 'string';
2094 2095
 	/**
2095
-	 * Boolean. `value` is `boolean`.
2096
+	 * Boolean. `$value` is `bool`.
2096 2097
 	 */
2097 2098
 	public const TYPE_BOOLEAN = 'boolean';
2098 2099
 	/**
2099 2100
 	 * A formula that has resulted in an error during parsing or evaluation.
2100
-	 * `value` is `string` error message.
2101
+	 * `$value` is `string` error message.
2101 2102
 	 */
2102 2103
 	public const TYPE_ERROR = 'error';
2103 2104
 	/**
2104
-	 * A formula expression. `value` is `string` and includes the leading `=`.
2105
+	 * A formula expression. `$value` is `string` and includes the leading `=`.
2105 2106
 	 */
2106 2107
 	public const TYPE_FORMULA = 'formula';
2107 2108
 
@@ -2136,10 +2137,10 @@ class CellValue {
2136 2137
 		string $type = CellValue::TYPE_STRING,
2137 2138
 		int $decimals = 0
2138 2139
 	) {
2139
-		$this->formattedValue = formattedValue;
2140
-		$this->value = value;
2141
-		$this->type = type;
2142
-		$this->decimals = decimals;
2140
+		$this->formattedValue = $formattedValue;
2141
+		$this->value = $value;
2142
+		$this->type = $type;
2143
+		$this->decimals = $decimals;
2143 2144
 	}
2144 2145
 
2145 2146
 	/**
@@ -2176,16 +2177,16 @@ class CellValue {
2176 2177
 		}
2177 2178
 		if ($value instanceof Error) {
2178 2179
 			if ($value instanceof CellException) {
2179
-				return new CellValue($value->errorSymbol, $value->message, CellValue::TYPE_ERROR);
2180
+				return new CellValue($value->errorSymbol, $value->getMessage(), CellValue::TYPE_ERROR);
2180 2181
 			}
2181
-			return new CellValue('#ERROR', $value->message, CellValue::TYPE_ERROR);
2182
+			return new CellValue('#ERROR', $value->getMessage(), CellValue::TYPE_ERROR);
2182 2183
 		}
2183 2184
 		if (is_bool($value)) {
2184 2185
 			$formatted = CellValue::formatType($value, CellValue::TYPE_BOOLEAN, 0);
2185 2186
 			return new CellValue($formatted, $value, CellValue::TYPE_BOOLEAN);
2186 2187
 		}
2187 2188
 		if (is_numeric($value)) {
2188
-			$resolvedType = $type || CellValue::TYPE_NUMBER;
2189
+			$resolvedType = $type ?? CellValue::TYPE_NUMBER;
2189 2190
 			$resolvedDecimals = ($decimals !== null) ? $decimals :
2190 2191
 				($resolvedType == CellValue::TYPE_CURRENCY ? 2 :
2191 2192
 					CellValue::autodecimals($resolvedType == CellValue::TYPE_PERCENT ?
@@ -2298,7 +2299,7 @@ class CellValue {
2298 2299
 			$wholes = mb_eregi_replace(',', '', $groups[1]);
2299 2300
 			$this->type = CellValue::TYPE_NUMBER;
2300 2301
 			$this->decimals = 0;
2301
-			$this->value = parseFloat($wholes);
2302
+			$this->value = floatval($wholes);
2302 2303
 			$this->formattedValue = CellValue::formatNumber($this->value, $this->decimals);
2303 2304
 			return;
2304 2305
 		}
@@ -2358,7 +2359,7 @@ class CellValue {
2358 2359
 			case CellValue::TYPE_CURRENCY:
2359 2360
 			case CellValue::TYPE_NUMBER:
2360 2361
 			case CellValue::TYPE_PERCENT:
2361
-				return $formatted ? $this->formattedValue : "${$this->value}";
2362
+				return $formatted ? $this->formattedValue : "{$this->value}";
2362 2363
 			case CellValue::TYPE_STRING:
2363 2364
 				return $formatted ? $this->formattedValue : $this->value;
2364 2365
 			case CellValue::TYPE_ERROR:
@@ -2522,23 +2523,23 @@ class CellValue {
2522 2523
 	 * @param CellValue $a  operand A
2523 2524
 	 * @param CellValue $b  operand B
2524 2525
 	 * @param string $op  operator symbol
2525
-	 * @return array  3-element tuple array with A number value, B number value,
2526
+	 * @return array  3-element tuple array with A float value, B float value,
2526 2527
 	 *   and result type string
2527 2528
 	 * @throws CellEvaluationException  if types are incompatible for numeric operations
2528 2529
 	 */
2529 2530
 	private static function resolveNumericOperands(CellValue $a, CellValue $b, string $op): array {
2530
-		if ($a->type == $this->TYPE_ERROR) throw $a->value;
2531
-		if ($b->type == $this->TYPE_ERROR) throw $b->value;
2532
-		if ($a->type == $this->TYPE_STRING || $b->type == $this->TYPE_STRING) {
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) {
2533 2534
 			throw new CellEvaluationException("Cannot perform math on text values");
2534 2535
 		}
2535 2536
 
2536
-		if ($a->type == $this->TYPE_BLANK) {
2537
-			if ($b->type == $this->TYPE_BLANK) {
2538
-				return [ 0, 0, $this->TYPE_NUMBER, 0 ];
2537
+		if ($a->type == self::TYPE_BLANK) {
2538
+			if ($b->type == self::TYPE_BLANK) {
2539
+				return [ 0, 0, self::TYPE_NUMBER, 0 ];
2539 2540
 			}
2540 2541
 			return [ 0, $b->value, $b->type ];
2541
-		} elseif ($b->type == $this->TYPE_BLANK) {
2542
+		} elseif ($b->type == self::TYPE_BLANK) {
2542 2543
 			return [ $a->value, 0, $a->type ];
2543 2544
 		}
2544 2545
 
@@ -2549,30 +2550,30 @@ class CellValue {
2549 2550
 		}
2550 2551
 
2551 2552
 		switch ($a->type . $b->type) {
2552
-			case $this->TYPE_CURRENCY . $this->TYPE_NUMBER:
2553
-			case $this->TYPE_CURRENCY . $this->TYPE_PERCENT:
2554
-				return [ $a->value, $b->value, $this->TYPE_CURRENCY ];
2555
-			case $this->TYPE_PERCENT . $this->TYPE_CURRENCY:
2553
+			case self::TYPE_CURRENCY . self::TYPE_NUMBER:
2554
+			case self::TYPE_CURRENCY . self::TYPE_PERCENT:
2555
+				return [ $a->value, $b->value, self::TYPE_CURRENCY ];
2556
+			case self::TYPE_PERCENT . self::TYPE_CURRENCY:
2556 2557
 				return [ $a->value, $b->value,
2557
-						$isMultOrDiv ? $this->TYPE_CURRENCY : $this->TYPE_PERCENT ];
2558
-			case $this->TYPE_PERCENT . $this->TYPE_NUMBER:
2558
+						$isMultOrDiv ? self::TYPE_CURRENCY : self::TYPE_PERCENT ];
2559
+			case self::TYPE_PERCENT . self::TYPE_NUMBER:
2559 2560
 				return [ $a->value, $b->value,
2560
-						$isMultOrDiv ? $this->TYPE_NUMBER : $this->TYPE_PERCENT ];
2561
-			case $this->TYPE_NUMBER . $this->TYPE_CURRENCY:
2561
+						$isMultOrDiv ? self::TYPE_NUMBER : self::TYPE_PERCENT ];
2562
+			case self::TYPE_NUMBER . self::TYPE_CURRENCY:
2562 2563
 				return [ $a->value, $b->value, $b->type ];
2563
-			case $this->TYPE_NUMBER . $this->TYPE_PERCENT:
2564
+			case self::TYPE_NUMBER . self::TYPE_PERCENT:
2564 2565
 				return [ $a->value, $b->value,
2565
-						$isMultOrDiv ? $this->TYPE_NUMBER : $b->type ];
2566
-			case $this->TYPE_BOOLEAN . $this->TYPE_CURRENCY:
2567
-			case $this->TYPE_BOOLEAN . $this->TYPE_NUMBER:
2568
-			case $this->TYPE_BOOLEAN . $this->TYPE_PERCENT:
2566
+						$isMultOrDiv ? self::TYPE_NUMBER : $b->type ];
2567
+			case self::TYPE_BOOLEAN . self::TYPE_CURRENCY:
2568
+			case self::TYPE_BOOLEAN . self::TYPE_NUMBER:
2569
+			case self::TYPE_BOOLEAN . self::TYPE_PERCENT:
2569 2570
 				return [ $a->value ? 1 : 0, $b->value, $b->type ];
2570
-			case $this->TYPE_CURRENCY . $this->TYPE_BOOLEAN:
2571
-			case $this->TYPE_NUMBER . $this->TYPE_BOOLEAN:
2572
-			case $this->TYPE_PERCENT . $this->TYPE_BOOLEAN:
2571
+			case self::TYPE_CURRENCY . self::TYPE_BOOLEAN:
2572
+			case self::TYPE_NUMBER . self::TYPE_BOOLEAN:
2573
+			case self::TYPE_PERCENT . self::TYPE_BOOLEAN:
2573 2574
 				return [ $a->value, $b->value ? 1 : 0, $a->type ];
2574 2575
 		}
2575
-		throw new CellEvaluationException(`Unhandled operand types "${a.type}" and "${b.type}"`);
2576
+		throw new CellEvaluationException("Unhandled operand types \"{$a->type}\" and \"{$b->type}\"");
2576 2577
 	}
2577 2578
 
2578 2579
 	/**
@@ -2648,6 +2649,7 @@ class CellValue {
2648 2649
 			case CellValue::TYPE_BOOLEAN: return $value ? 'TRUE' : 'FALSE';
2649 2650
 			case CellValue::TYPE_STRING: return "{$value}";
2650 2651
 			case CellValue::TYPE_FORMULA: return "{$value}";
2652
+			default: throw new CellException("Cannot format value of type {$type}");
2651 2653
 		}
2652 2654
 	}
2653 2655
 
@@ -2660,7 +2662,7 @@ class CellValue {
2660 2662
 		if (str_starts_with($s, '-')) {
2661 2663
 			return '-$' . mb_substr($s, 1);
2662 2664
 		}
2663
-		return '$' . s;
2665
+		return '$' . $s;
2664 2666
 	}
2665 2667
 
2666 2668
 	private static function formatPercent(float|int $value, int $decimals): string {
@@ -2679,6 +2681,7 @@ class CellValue {
2679 2681
 			$s = number_format($value, $maxDigits);
2680 2682
 			if (strpos($s, '.') === false) return 0;
2681 2683
 			$fraction = explode('.', $s)[1];
2684
+			$fraction = rtrim($fraction, '0');
2682 2685
 			return min($maxDigits, mb_strlen($fraction));
2683 2686
 		}
2684 2687
 		return 0;
@@ -2830,7 +2833,7 @@ class MDSpreadsheetReader extends MDReader {
2830 2833
 		// Copy results back to table
2831 2834
 		for ($c = 0; $c < $columnCount; $c++) {
2832 2835
 			for ($r = 0; $r < $rowCount; $r++) {
2833
-				$cellNode = $tableNode->bodyRows[$r]->children[$c];
2836
+				$cellNode = $tableNode->bodyRows()[$r]->children[$c];
2834 2837
 				if ($cellNode === null) continue;
2835 2838
 				$gridCell = $grid->cells[$c][$r];
2836 2839
 				$gridValue = $gridCell->outputValue;

+ 22
- 10
playgroundapi.php Прегледај датотеку

@@ -22,6 +22,7 @@ $permittedReaders = [
22 22
 	'MDFootnoteReader' => true,
23 23
 	'MDAbbreviationReader' => true,
24 24
 	'MDParagraphReader' => true,
25
+	'MDSpreadsheetReader' => true,
25 26
 
26 27
 	'MDEmphasisReader' => true,
27 28
 	'MDStrongReader' => true,
@@ -41,16 +42,27 @@ $permittedReaders = [
41 42
 ];
42 43
 
43 44
 include 'php/markdown.php';
44
-$readers = [];
45
-foreach ($readerNames as $readerName) {
46
-	if ($permittedReaders[$readerName] ?? false) {
47
-		$ref = new ReflectionClass($readerName);
48
-		$reader = $ref->newInstanceArgs([]);
49
-		array_push($readers, $reader);
45
+include 'php/spreadsheet.php';
46
+try {
47
+	$readers = [];
48
+	foreach ($readerNames as $readerName) {
49
+		if ($permittedReaders[$readerName] ?? false) {
50
+			$ref = new ReflectionClass($readerName);
51
+			$reader = $ref->newInstanceArgs([]);
52
+			array_push($readers, $reader);
53
+		}
50 54
 	}
55
+	$parser = new Markdown($readers);
56
+	$html = $parser->toHTML($markdown);
57
+	header('Content-Type: text/html');
58
+	print($html);
59
+} catch (Error $e) {
60
+	header('Content-Type: text/html');
61
+	print('<pre><code>');
62
+	print($e->getMessage() . "\n");
63
+	print($e->getTraceAsString() . "\n");
64
+	print('</code></pre>');
65
+	flush();
66
+	throw $e;
51 67
 }
52
-$parser = new Markdown($readers);
53
-$html = $parser->toHTML($markdown);
54
-header('Content-Type: text/html');
55
-print($html);
56 68
 ?>

Loading…
Откажи
Сачувај