PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
← All changes | libraries/vendor/PhpSpreadsheet/Reader/Ods.php +167 -44 3.3.1 → 3.4 View file →
@@ -1,9 +1,8 @@
1 1 <?php
2 2
3 3 namespace TablePress\PhpOffice\PhpSpreadsheet\Reader;
4 4
5 -use Closure;
6 5 use TablePress\Composer\Pcre\Preg;
7 6 use DateTime;
8 7 use DateTimeZone;
9 8 use DOMAttr;
@@ -31,8 +30,9 @@
31 30 use TablePress\PhpOffice\PhpSpreadsheet\Style\Borders;
32 31 use TablePress\PhpOffice\PhpSpreadsheet\Style\Fill;
33 32 use TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat;
34 33 use TablePress\PhpOffice\PhpSpreadsheet\Style\Protection;
34 +use TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
35 35 use TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
36 36 use Throwable;
37 37 use XMLReader;
38 38 use ZipArchive;
@@ -40,8 +40,12 @@
40 40 class Ods extends BaseReader
41 41 {
42 42 const INITIAL_FILE = 'content.xml';
43 43
44 + private ZipArchive $zip;
45 +
46 + private string $filename;
47 +
44 48 /**
45 49 * Create a new Ods Reader instance.
46 50 */
47 51 public function __construct()
@@ -209,8 +213,9 @@
209 213 $xml->read();
210 214 if ($xml->name == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) {
211 215 $rowspan = $xml->getAttribute('table:number-rows-repeated');
212 216 $rowspan = empty($rowspan) ? 1 : (int) $rowspan;
217 + self::checkRowsRepeated($currRow, $rowspan);
213 218 $currRow += $rowspan;
214 219 $currCol = 0;
215 220 // Step into the row
216 221 $xml->read();
@@ -218,8 +223,12 @@
218 223 $doread = true;
219 224 if ($xml->name == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
220 225 $mergeSize = $xml->getAttribute('table:number-columns-repeated');
221 226 $mergeSize = empty($mergeSize) ? 1 : (int) $mergeSize;
227 + self::checkColumnsRepeatedInt(
228 + $currCol,
229 + $mergeSize
230 + );
222 231 $currCol += $mergeSize;
223 232 if (!$xml->isEmptyElement) {
224 233 $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCol);
225 234 $tmpInfo['totalRows'] = $currRow;
@@ -227,8 +236,13 @@
227 236 $doread = false;
228 237 }
229 238 } elseif ($xml->name == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
230 239 $mergeSize = $xml->getAttribute('table:number-columns-repeated');
240 + $mergeSize = empty($mergeSize) ? 1 : (int) $mergeSize;
241 + self::checkColumnsRepeatedInt(
242 + $currCol,
243 + $mergeSize
244 + );
231 245 $currCol += (int) $mergeSize;
232 246 }
233 247 if ($doread) {
234 248 $xml->read();
@@ -281,8 +295,9 @@
281 295 * shrinkToFit?: bool,
282 296 * textRotation?: int,
283 297 * vertical?: string,
284 298 * wrapText?: bool,
299 + * indent?: int,
285 300 * },
286 301 * protection?:array{
287 302 * locked?: string,
288 303 * hidden?: string,
@@ -311,9 +326,10 @@
311 326 public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet
312 327 {
313 328 File::assertFile($filename, self::INITIAL_FILE);
314 329
315 - $zip = new ZipArchive();
330 + $this->zip = $zip = new ZipArchive();
331 + $this->filename = $filename;
316 332 $zip->open($filename);
317 333
318 334 // Meta
319 335
@@ -321,9 +337,9 @@
321 337 $this->getSecurityScannerOrThrow()
322 338 ->scan($zip->getFromName('meta.xml'))
323 339 );
324 340 if ($xml === false) {
325 - throw new Exception('Unable to read data from {$pFilename}');
341 + throw new Exception("Unable to read data from {$filename}");
326 342 }
327 343
328 344 /** @var array{meta?: string, office?: string, dc?: string} */
329 345 $namespacesMeta = $xml->getNamespaces(true);
@@ -332,13 +348,9 @@
332 348
333 349 // Styles
334 350
335 351 $this->allStyles = $this->numberFormats = [];
336 - $dom = new DOMDocument('1.01', 'UTF-8');
337 - $dom->loadXML(
338 - $this->getSecurityScannerOrThrow()
339 - ->scan($zip->getFromName('styles.xml'))
340 - );
352 + $dom = $this->loadDom('styles.xml', $zip);
341 353 $officeNs = (string) $dom->lookupNamespaceUri('office');
342 354 $styleNs = (string) $dom->lookupNamespaceUri('style');
343 355 $fontNs = (string) $dom->lookupNamespaceUri('fo');
344 356 $numberNs = (string) $dom->lookupNamespaceUri('number');
@@ -402,13 +414,9 @@
402 414 $pageSettings = new PageSettings($dom);
403 415
404 416 // Main Content
405 417
406 - $dom = new DOMDocument('1.01', 'UTF-8');
407 - $dom->loadXML(
408 - $this->getSecurityScannerOrThrow()
409 - ->scan($zip->getFromName(self::INITIAL_FILE))
410 - );
418 + $dom = $this->loadDom(self::INITIAL_FILE, $zip);
411 419
412 420 $pageSettings->readStyleCrossReferences($dom);
413 421
414 422 $autoFilterReader = new AutoFilter($spreadsheet, $tableNs);
@@ -511,9 +519,9 @@
511 519 // Go through every child of table element
512 520 $rowID = 1;
513 521 $tableColumnIndex = 1;
514 522 $this->highestDataIndex = AddressRange::MAX_COLUMN_INT;
515 - foreach ($worksheetDataSet->childNodes as $childNode) {
523 + foreach ($worksheetDataSet->childNodes ?? [] as $childNode) {
516 524 /** @var DOMElement $childNode */
517 525
518 526 // Filter elements which are not under the "table" ns
519 527 if ($childNode->namespaceURI != $tableNs) {
@@ -642,9 +650,9 @@
642 650
643 651 // Go through every child of table element processing column widths
644 652 $rowID = 1;
645 653 $tableColumnIndex = 1;
646 - foreach ($worksheetDataSet->childNodes as $childNode) {
654 + foreach ($worksheetDataSet->childNodes ?? [] as $childNode) {
647 655 /** @var DOMElement $childNode */
648 656 if (empty($columnWidths) || $this->readEmptyCells) {
649 657 break;
650 658 }
@@ -722,9 +730,9 @@
722 730 string $textNs,
723 731 string $xlinkNs,
724 732 Spreadsheet $spreadsheet
725 733 ): void {
726 - foreach ($childNode->childNodes as $grandchildNode) {
734 + foreach ($childNode->childNodes ?? [] as $grandchildNode) {
727 735 /** @var DOMElement $grandchildNode */
728 736 $grandkey = self::extractNodeName($grandchildNode->nodeName);
729 737 switch ($grandkey) {
730 738 case 'table-row':
@@ -753,9 +761,9 @@
753 761 string $textNs,
754 762 string $xlinkNs,
755 763 Spreadsheet $spreadsheet
756 764 ): void {
757 - foreach ($childNode->childNodes as $grandchildNode) {
765 + foreach ($childNode->childNodes ?? [] as $grandchildNode) {
758 766 /** @var DOMElement $grandchildNode */
759 767 $grandkey = self::extractNodeName($grandchildNode->nodeName);
760 768 switch ($grandkey) {
761 769 case 'table-row':
@@ -816,13 +824,14 @@
816 824 $rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-rows-repeated');
817 825 } else {
818 826 $rowRepeats = 1;
819 827 }
828 + self::checkRowsRepeated($rowID, $rowRepeats);
820 829 $worksheet = $spreadsheet->getSheetByName($worksheetName);
821 830
822 831 $columnID = 'A';
823 832 /** @var DOMElement|DOMText $cellData */
824 - foreach ($childNode->childNodes as $cellData) {
833 + foreach ($childNode->childNodes ?? [] as $cellData) {
825 834 if ($cellData instanceof DOMText) {
826 835 continue; // should just be whitespace
827 836 }
828 837 if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
@@ -829,8 +838,10 @@
829 838 $colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
830 839 } else {
831 840 $colRepeats = 1;
832 841 }
842 + $columnIndex = Coordinate::columnIndexFromString($columnID);
843 + self::checkColumnsRepeated($columnID, $colRepeats);
833 844 $styleName = $cellData->getAttributeNS($tableNs, 'style-name');
834 845 if ($styleName === '') {
835 846 if ($worksheet === null || !$worksheet->columnDimensionExists($columnID)) {
836 847 $assignedNumberFormat = '';
@@ -850,9 +861,9 @@
850 861 // When a cell has number-columns-repeated, check if ANY column in the
851 862 // repeated range passes the read filter. If not, skip the entire group.
852 863 // If some columns pass, we need to fall through to the processing block
853 864 // which will handle per-column filtering.
854 - if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
865 + if (!$this->readFilter->readCell($columnID, $rowID, $worksheetName)) {
855 866 if ($colRepeats <= 1) {
856 867 StringHelper::stringIncrement($columnID);
857 868
858 869 continue;
@@ -864,9 +875,9 @@
864 875 for ($i = 0; $i < $colRepeats; ++$i) {
865 876 if ($i > 0) {
866 877 StringHelper::stringIncrement($tempCol);
867 878 }
868 - if ($this->getReadFilter()->readCell($tempCol, $rowID, $worksheetName)) {
879 + if ($this->readFilter->readCell($tempCol, $rowID, $worksheetName)) {
869 880 $anyColumnPasses = true;
870 881
871 882 break;
872 883 }
@@ -880,8 +891,10 @@
880 891 continue;
881 892 }
882 893 // Fall through to process the cell, with per-column filter checks
883 894 }
895 + $tempSpannedRange = "$columnID$rowID";
896 + $spannedRange = '';
884 897 if ($worksheet !== null && ($cellData->hasChildNodes() || ($cellData->nextSibling !== null)) && isset($this->allStyles[$styleName])) {
885 898 $spannedRange = "$columnID$rowID";
886 899 // the following is sufficient for ods,
887 900 // and does no harm for xlsx/xls.
@@ -970,14 +983,16 @@
970 983
971 984 /** @var DOMElement[] $paragraphs */
972 985 $paragraphs = [];
973 986
974 - foreach ($cellData->childNodes as $item) {
987 + foreach ($cellData->childNodes ?? [] as $item) {
975 988 /** @var DOMElement $item */
976 989
977 990 // Filter text:p elements
978 991 if ($item->nodeName == 'text:p') {
979 992 $paragraphs[] = $item;
993 + } elseif ($item->nodeName === 'draw:frame' && $worksheet !== null) {
994 + $this->processDrawFrame($spannedRange ?: $tempSpannedRange, $item, $worksheet);
980 995 }
981 996 }
982 997
983 998 if (count($paragraphs) > 0) {
@@ -1080,19 +1095,22 @@
1080 1095 case 'date':
1081 1096 $type = DataType::TYPE_NUMERIC;
1082 1097 $value = $cellData->getAttributeNS($officeNs, 'date-value');
1083 1098 $dataValue = Date::convertIsoDate($value);
1099 + $format15 = Preg::isMatch('/\d\d\d\d/', $allCellDataText) ? NumberFormat::FORMAT_DATE_XLSX15_YYYY : NumberFormat::FORMAT_DATE_XLSX15;
1084 1100
1085 1101 if (Preg::isMatch('/^\d\d\d\d-\d\d-\d\d$/', $allCellDataText)) {
1086 1102 $formatting = 'yyyy-mm-dd';
1103 + } elseif (Preg::isMatch('/^\d\d\d\d-\d\d-\d\d \d\d:\d\d(:\d\d)?$/', $allCellDataText)) {
1104 + $formatting = NumberFormat::FORMAT_DATE_DATETIME_BETTER;
1087 1105 } elseif (Preg::isMatch('/^\d\d?-[a-zA-Z]+-\d\d\d\d$/', $allCellDataText)) {
1088 1106 $formatting = 'd-mmm-yyyy';
1089 - } elseif ($dataValue != floor($dataValue)) {
1090 - $formatting = NumberFormat::FORMAT_DATE_XLSX15
1107 + } elseif ($dataValue != floor($dataValue) || str_contains($allCellDataText, ':')) {
1108 + $formatting = $format15
1091 1109 . ' '
1092 1110 . NumberFormat::FORMAT_DATE_TIME4;
1093 1111 } else {
1094 - $formatting = NumberFormat::FORMAT_DATE_XLSX15;
1112 + $formatting = $format15;
1095 1113 }
1096 1114
1097 1115 break;
1098 1116 case 'time':
@@ -1142,9 +1160,9 @@
1142 1160 if ($i > 0) {
1143 1161 StringHelper::stringIncrement($columnID);
1144 1162 }
1145 1163
1146 - if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
1164 + if (!$this->readFilter->readCell($columnID, $rowID, $worksheetName)) {
1147 1165 continue;
1148 1166 }
1149 1167
1150 1168 if ($type !== DataType::TYPE_NULL) {
@@ -1199,8 +1217,63 @@
1199 1217 }
1200 1218 $rowID += $rowRepeats;
1201 1219 }
1202 1220
1221 + private function processDrawFrame(string $spannedRange, DOMElement $item, Worksheet $worksheet): void
1222 + {
1223 + $drawName = $item->getAttribute('draw:name');
1224 + $svgWidth = $item->getAttribute('svg:width');
1225 + $svgHeight = $item->getAttribute('svg:height');
1226 + $styleName = $item->getAttribute('draw:style-name');
1227 + $drawImage = null;
1228 + foreach ($item->childNodes ?? [] as $node) {
1229 + // Check if the node is a standard element tag
1230 + if ($node->nodeType === XML_ELEMENT_NODE && $node->nodeName === 'draw:image') {
1231 + /** @var DOMElement */
1232 + $drawImage = $node;
1233 +
1234 + break;
1235 + }
1236 + }
1237 +
1238 + $xlinkHref = $xlinkType = $xlinkShow = '';
1239 + if ($drawImage !== null) {
1240 + $xlinkHref = $drawImage->getAttribute('xlink:href');
1241 + $xlinkType = $drawImage->getAttribute('xlink:type');
1242 + $xlinkShow = $drawImage->getAttribute('xlink:show');
1243 + }
1244 + if (
1245 + $drawName !== ''
1246 + && Preg::isMatch('/(\d+([.]\d+)?)(cm|in)/', $svgWidth, $matchWidth)
1247 + && Preg::isMatch('/(\d+([.]\d+)?)(cm|in)/', $svgHeight, $matchHeight)
1248 + //&& $styleName === 'gr1'
1249 + && (str_starts_with($xlinkHref, 'Pictures/') || str_starts_with($xlinkHref, 'media/'))
1250 + && $xlinkType === 'simple'
1251 + && $xlinkShow === 'embed'
1252 + ) {
1253 + $drawing = new Drawing();
1254 + $drawing->setPath(
1255 + "zip://{$this->filename}#$xlinkHref",
1256 + true,
1257 + $this->zip,
1258 + false
1259 + );
1260 + $unit = [
1261 + 'cm' => HelperDimension::ABSOLUTE_UNITS[HelperDimension::UOM_CENTIMETERS],
1262 + 'in' => HelperDimension::ABSOLUTE_UNITS[HelperDimension::UOM_INCHES],
1263 + ];
1264 + $width = ((float) $matchWidth[1]) * $unit[$matchWidth[3]];
1265 + $height = ((float) $matchHeight[1]) * $unit[$matchHeight[3]];
1266 + if ($drawing->getPath()) {
1267 + $drawing->setCoordinates($spannedRange)
1268 + ->setWidth((int) $width)
1269 + ->setHeight((int) $height)
1270 + ->setName($drawName)
1271 + ->setWorksheet($worksheet);
1272 + }
1273 + }
1274 + }
1275 +
1203 1276 private static function extractNodeName(string $key): string
1204 1277 {
1205 1278 // Remove ns from node name
1206 1279 if (str_contains($key, ':')) {
@@ -1222,9 +1295,9 @@
1222 1295 Spreadsheet $spreadsheet,
1223 1296 bool $processWidths = true,
1224 1297 bool $processStyles = true
1225 1298 ): void {
1226 - foreach ($childNode->childNodes as $grandchildNode) {
1299 + foreach ($childNode->childNodes ?? [] as $grandchildNode) {
1227 1300 /** @var DOMElement $grandchildNode */
1228 1301 $grandkey = self::extractNodeName($grandchildNode->nodeName);
1229 1302 switch ($grandkey) {
1230 1303 case 'table-column':
@@ -1254,9 +1327,9 @@
1254 1327 Spreadsheet $spreadsheet,
1255 1328 bool $processWidths = true,
1256 1329 bool $processStyles = true
1257 1330 ): void {
1258 - foreach ($childNode->childNodes as $grandchildNode) {
1331 + foreach ($childNode->childNodes ?? [] as $grandchildNode) {
1259 1332 /** @var DOMElement $grandchildNode */
1260 1333 $grandkey = self::extractNodeName($grandchildNode->nodeName);
1261 1334 switch ($grandkey) {
1262 1335 case 'table-column':
@@ -1312,12 +1385,14 @@
1312 1385 bool $processWidths = true,
1313 1386 bool $processStyles = true
1314 1387 ): void {
1315 1388 if ($childNode->hasAttributeNS($tableNs, 'number-columns-repeated')) {
1316 - $rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-columns-repeated');
1389 + $colRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-columns-repeated');
1317 1390 } else {
1318 - $rowRepeats = 1;
1391 + $colRepeats = 1;
1319 1392 }
1393 + // called routine expects index to be 1 less than it is
1394 + self::checkColumnsRepeatedInt($tableColumnIndex - 1, $colRepeats);
1320 1395 $tableStyleName = $childNode->getAttributeNS($tableNs, 'style-name');
1321 1396 if ($processWidths) {
1322 1397 if (isset($columnWidths[$tableStyleName])) {
1323 1398 $columnWidth = new HelperDimension($columnWidths[$tableStyleName]);
@@ -1322,9 +1397,9 @@
1322 1397 if (isset($columnWidths[$tableStyleName])) {
1323 1398 $columnWidth = new HelperDimension($columnWidths[$tableStyleName]);
1324 1399 $tableColumnIndex2 = $tableColumnIndex;
1325 1400 $tableColumnString = Coordinate::stringFromColumnIndex($tableColumnIndex2);
1326 - for ($rowRepeats2 = $rowRepeats; $rowRepeats2 > 0 && $tableColumnIndex2 <= AddressRange::MAX_COLUMN_INT; --$rowRepeats2) {
1401 + for ($colRepeats2 = $colRepeats; $colRepeats2 > 0 && $tableColumnIndex2 <= AddressRange::MAX_COLUMN_INT; --$colRepeats2) {
1327 1402 if (!$this->readEmptyCells && $tableColumnIndex2 > $this->highestDataIndex) {
1328 1403 break;
1329 1404 }
1330 1405 $spreadsheet->getActiveSheet()
@@ -1341,9 +1416,9 @@
1341 1416 $defaultStyleName = $childNode->getAttributeNS($tableNs, 'default-cell-style-name');
1342 1417 if ($defaultStyleName !== 'Default' && isset($this->allStyles[$defaultStyleName])) {
1343 1418 $tableColumnIndex2 = $tableColumnIndex;
1344 1419 $tableColumnString = Coordinate::stringFromColumnIndex($tableColumnIndex2);
1345 - for ($rowRepeats2 = $rowRepeats; $rowRepeats2 > 0 && $tableColumnIndex2 <= AddressRange::MAX_COLUMN_INT; --$rowRepeats2) {
1420 + for ($colRepeats2 = $colRepeats; $colRepeats2 > 0 && $tableColumnIndex2 <= AddressRange::MAX_COLUMN_INT; --$colRepeats2) {
1346 1421 $spreadsheet->getActiveSheet()
1347 1422 ->getStyle($tableColumnString)
1348 1423 ->applyFromArray(
1349 1424 $this->allStyles[$defaultStyleName]
@@ -1354,18 +1429,14 @@
1354 1429 ++$tableColumnIndex2;
1355 1430 }
1356 1431 }
1357 1432 }
1358 - $tableColumnIndex += $rowRepeats;
1433 + $tableColumnIndex += $colRepeats;
1359 1434 }
1360 1435
1361 1436 private function processSettings(ZipArchive $zip, Spreadsheet $spreadsheet): void
1362 1437 {
1363 - $dom = new DOMDocument('1.01', 'UTF-8');
1364 - $dom->loadXML(
1365 - $this->getSecurityScannerOrThrow()
1366 - ->scan($zip->getFromName('settings.xml'))
1367 - );
1438 + $dom = $this->loadDom('settings.xml', $zip);
1368 1439 $configNs = (string) $dom->lookupNamespaceUri('config');
1369 1440 $officeNs = (string) $dom->lookupNamespaceUri('office');
1370 1441 $settings = $dom->getElementsByTagNameNS($officeNs, 'settings')
1371 1442 ->item(0);
@@ -1431,9 +1502,9 @@
1431 1502 */
1432 1503 protected function scanElementForText(DOMNode $element): string
1433 1504 {
1434 1505 $str = '';
1435 - foreach ($element->childNodes as $child) {
1506 + foreach ($element->childNodes ?? [] as $child) {
1436 1507 /** @var DOMNode $child */
1437 1508 if ($child->nodeType == XML_TEXT_NODE) {
1438 1509 $str .= $child->nodeValue;
1439 1510 } elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:line-break') {
@@ -1510,13 +1581,13 @@
1510 1581 }
1511 1582 }
1512 1583 }
1513 1584
1514 - /** @var null|Closure(string, string):string */
1515 - private ?Closure $formatCallback = null;
1585 + /** @var null|callable(string, string):string format callback routine */
1586 + private $formatCallback;
1516 1587
1517 - /** @param Closure(string, string):string $formatCallback */
1518 - public function setFormatCallback(Closure $formatCallback): void
1588 + /** @param callable(string, string):string $formatCallback format callback routine */
1589 + public function setFormatCallback(callable $formatCallback): void
1519 1590 {
1520 1591 $this->formatCallback = $formatCallback;
1521 1592 }
1522 1593
@@ -1649,8 +1720,9 @@
1649 1720
1650 1721 /** @return array{
1651 1722 * horizontal?: string,
1652 1723 * readOrder?: int,
1724 + * indent?: int,
1653 1725 * }
1654 1726 */
1655 1727 protected function getAlignment2Styles(DOMElement $paragraphProperties, string $styleNs, string $fontNs): array
1656 1728 {
@@ -1749,9 +1821,9 @@
1749 1821 $borders = [];
1750 1822 $temp = $tableCellProperties->getAttributeNs($fontNs, 'border');
1751 1823 $diagonalIndex = Borders::DIAGONAL_NONE;
1752 1824 foreach (['bottom', 'left', 'right', 'top', 'diagonal-tl-br', 'diagonal-bl-tr'] as $direction) {
1753 - if (str_starts_with($direction, 'diagonal')) {
1825 + if ($direction === 'diagonal-tl-br' || $direction === 'diagonal-bl-tr') {
1754 1826 $directionIndex = 'diagonal';
1755 1827 $temp = $tableCellProperties->getAttributeNs($styleNs, $direction);
1756 1828 } else {
1757 1829 $directionIndex = $direction;
@@ -1777,9 +1849,9 @@
1777 1849 if ($diagonalIndex !== Borders::DIAGONAL_NONE) {
1778 1850 $borders['diagonalDirection'] = $diagonalIndex;
1779 1851 }
1780 1852
1781 - return $borders; // @phpstan-ignore-line
1853 + return $borders;
1782 1854 }
1783 1855
1784 1856 protected function processSomeNumberFormats(?DOMElement $automaticStyle0, string $numberNs, string $styleNs): void
1785 1857 {
@@ -1798,6 +1870,57 @@
1798 1870 if ($decimalPlaces === '0' && $minIntegerDigits > 1) {
1799 1871 $this->numberFormats[$styleName] = str_repeat('0', $minIntegerDigits);
1800 1872 }
1801 1873 }
1874 + }
1875 +
1876 + private static function checkRowsRepeated(int $rowID, int $rowRepeats): void
1877 + {
1878 + if ($rowRepeats < 1 || $rowID + $rowRepeats - 1 > AddressRange::MAX_ROW) {
1879 + throw new Exception("Invalid number-rows-repeated $rowRepeats following row $rowID");
1880 + }
1881 + }
1882 +
1883 + private static function checkColumnsRepeated(string $colID, int $colRepeats): void
1884 + {
1885 + $colIndex = Coordinate::columnIndexFromString($colID);
1886 + if ($colRepeats < 1 || $colIndex + $colRepeats - 1 > AddressRange::MAX_COLUMN_INT) {
1887 + throw new Exception("Invalid number-columns-repeated $colRepeats following column $colID");
1888 + }
1889 + }
1890 +
1891 + private static function checkColumnsRepeatedInt(int $colIndex, int $colRepeats): void
1892 + {
1893 + // We don't have column string at this point,
1894 + // and colIndex is actually 1 less than it should be.
1895 + if ($colRepeats < 1 || $colIndex + $colRepeats > AddressRange::MAX_COLUMN_INT) {
1896 + throw new Exception("Invalid number-columns-repeated $colRepeats following column index $colIndex");
1897 + }
1898 + }
1899 +
1900 + private function loadDom(string $file, ZipArchive $zip): DOMDocument
1901 + {
1902 + $dom = new DOMDocument('1.01', 'UTF-8');
1903 + $orig = false;
1904 +
1905 + try {
1906 + $orig = libxml_use_internal_errors(true);
1907 + $result = $dom->loadXML(
1908 + $this->getSecurityScannerOrThrow()
1909 + ->scan($zip->getFromName($file))
1910 + );
1911 + if ($result === false) {
1912 + $fatal = false;
1913 + foreach (libxml_get_errors() as $err) {
1914 + if ($err->level === LIBXML_ERR_FATAL) {
1915 + throw new Exception($err->message);
1916 + }
1917 + }
1918 + }
1919 + } finally {
1920 + libxml_clear_errors();
1921 + libxml_use_internal_errors($orig);
1922 + }
1923 +
1924 + return $dom;
1802 1925 }
1803 1926 }