52 private static $invalidCharacters = [
'*',
':',
'/',
'\\',
'?',
'[',
']'];
66 private $cellCollection;
73 private $rowDimensions = [];
80 private $defaultRowDimension;
87 private $columnDimensions = [];
94 private $defaultColumnDimension;
101 private $drawingCollection;
108 private $chartCollection;
136 private $pageMargins;
143 private $headerFooter;
164 private $styles = [];
171 private $conditionalStylesCollection = [];
178 private $cellCollectionIsSorted =
false;
185 private $breaks = [];
192 private $mergeCells = [];
199 private $protectedCells = [];
220 private $topLeftCell;
227 private $showGridlines =
true;
234 private $printGridlines =
false;
241 private $showRowColHeaders =
true;
248 private $showSummaryBelow =
true;
255 private $showSummaryRight =
true;
262 private $comments = [];
269 private $activeCell =
'A1';
276 private $selectedCells =
'A1';
283 private $cachedHighestColumn = 1;
290 private $cachedHighestRow = 1;
297 private $rightToLeft =
false;
304 private $hyperlinkCollection = [];
311 private $dataValidationCollection = [];
325 private $dirty =
true;
347 public function __construct(?
Spreadsheet $parent =
null, $pTitle =
'Worksheet')
350 $this->parent = $parent;
351 $this->setTitle($pTitle,
false);
353 $this->setCodeName($this->getTitle());
354 $this->setSheetState(self::SHEETSTATE_VISIBLE);
366 $this->drawingCollection =
new ArrayObject();
368 $this->chartCollection =
new ArrayObject();
375 $this->autoFilter =
new AutoFilter(
null, $this);
382 public function disconnectCells(): void
384 if ($this->cellCollection !==
null) {
385 $this->cellCollection->unsetWorksheetCells();
387 $this->cellCollection =
null;
391 $this->parent =
null;
397 public function __destruct()
401 $this->disconnectCells();
402 $this->rowDimensions = [];
410 public function getCellCollection()
412 return $this->cellCollection;
420 public static function getInvalidCharacters()
422 return self::$invalidCharacters;
432 private static function checkSheetCodeName($pValue)
435 if ($CharCount == 0) {
436 throw new Exception(
'Sheet code name cannot be empty.');
440 (str_replace(self::$invalidCharacters,
'', $pValue) !== $pValue) ||
444 throw new Exception(
'Invalid character found in sheet code name');
448 if ($CharCount > self::SHEET_TITLE_MAXIMUM_LENGTH) {
449 throw new Exception(
'Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH .
' characters allowed in sheet code name.');
462 private static function checkSheetTitle($pValue)
465 if (str_replace(self::$invalidCharacters,
'', $pValue) !== $pValue) {
466 throw new Exception(
'Invalid character found in sheet title');
471 throw new Exception(
'Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH .
' characters allowed in sheet title.');
484 public function getCoordinates($sorted =
true)
486 if ($this->cellCollection ==
null) {
491 return $this->cellCollection->getSortedCoordinates();
494 return $this->cellCollection->getCoordinates();
502 public function getRowDimensions()
504 return $this->rowDimensions;
512 public function getDefaultRowDimension()
514 return $this->defaultRowDimension;
522 public function getColumnDimensions()
524 return $this->columnDimensions;
532 public function getDefaultColumnDimension()
534 return $this->defaultColumnDimension;
542 public function getDrawingCollection()
544 return $this->drawingCollection;
552 public function getChartCollection()
554 return $this->chartCollection;
564 public function addChart(Chart $pChart, $iChartIndex =
null)
566 $pChart->setWorksheet($this);
567 if ($iChartIndex ===
null) {
568 $this->chartCollection[] = $pChart;
571 array_splice($this->chartCollection, $iChartIndex, 0, [$pChart]);
582 public function getChartCount()
584 return count($this->chartCollection);
594 public function getChartByIndex(
$index)
596 $chartCount = count($this->chartCollection);
597 if ($chartCount == 0) {
603 if (!isset($this->chartCollection[
$index])) {
607 return $this->chartCollection[
$index];
615 public function getChartNames()
618 foreach ($this->chartCollection as $chart) {
619 $chartNames[] = $chart->getName();
632 public function getChartByName($chartName)
634 $chartCount = count($this->chartCollection);
635 if ($chartCount == 0) {
638 foreach ($this->chartCollection as
$index => $chart) {
639 if ($chart->getName() == $chartName) {
640 return $this->chartCollection[
$index];
652 public function refreshColumnDimensions()
654 $currentColumnDimensions = $this->getColumnDimensions();
655 $newColumnDimensions = [];
657 foreach ($currentColumnDimensions as $objColumnDimension) {
658 $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension;
661 $this->columnDimensions = $newColumnDimensions;
671 public function refreshRowDimensions()
673 $currentRowDimensions = $this->getRowDimensions();
674 $newRowDimensions = [];
676 foreach ($currentRowDimensions as $objRowDimension) {
677 $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension;
680 $this->rowDimensions = $newRowDimensions;
690 public function calculateWorksheetDimension()
693 return 'A1:' . $this->getHighestColumn() . $this->getHighestRow();
701 public function calculateWorksheetDataDimension()
704 return 'A1:' . $this->getHighestDataColumn() . $this->getHighestDataRow();
712 public function calculateColumnWidths()
716 foreach ($this->getColumnDimensions() as $colDimension) {
717 if ($colDimension->getAutoSize()) {
718 $autoSizes[$colDimension->getColumnIndex()] = -1;
723 if (!empty($autoSizes)) {
726 foreach ($this->getMergeCells() as $cells) {
728 $isMergeCell[$cellReference] =
true;
733 foreach ($this->getCoordinates(
false) as $coordinate) {
734 $cell = $this->getCellOrNull($coordinate);
735 if ($cell !==
null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
737 $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]);
740 $isMergedButProceed =
false;
743 if ($isMerged && $cell->isMergeRangeValueCell()) {
744 $range = $cell->getMergeRange();
746 if ($rangeBoundaries[0] == 1) {
747 $isMergedButProceed =
true;
752 if (!$isMerged || $isMergedButProceed) {
756 $cell->getCalculatedValue(),
757 $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode()
760 $autoSizes[$this->cellCollection->getCurrentColumn()] = max(
761 (
float) $autoSizes[$this->cellCollection->getCurrentColumn()],
763 $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
765 $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(),
766 $this->getParent()->getDefaultStyle()->getFont()
774 foreach ($autoSizes as $columnIndex => $width) {
776 $width = $this->getDefaultColumnDimension()->getWidth();
778 $this->getColumnDimension($columnIndex)->setWidth($width);
790 public function getParent()
792 return $this->parent;
800 public function rebindParent(Spreadsheet $parent)
802 if ($this->parent !==
null) {
803 $definedNames = $this->parent->getDefinedNames();
804 foreach ($definedNames as $definedName) {
805 $parent->addDefinedName($definedName);
808 $this->parent->removeSheetByIndex(
809 $this->parent->getIndex($this)
812 $this->parent = $parent;
822 public function getTitle()
841 public function setTitle(
$title, $updateFormulaCellReferences =
true, $validate =
true)
844 if ($this->getTitle() ==
$title) {
849 $oldTitle = $this->getTitle();
853 self::checkSheetTitle(
$title);
857 if ($this->parent->sheetNameExists(
$title)) {
864 while ($this->parent->sheetNameExists(
$title .
' ' .
$i)) {
870 } elseif (
$i == 100) {
886 if ($this->parent && $this->parent->getCalculationEngine()) {
888 $newTitle = $this->getTitle();
889 $this->parent->getCalculationEngine()
890 ->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
891 if ($updateFormulaCellReferences) {
904 public function getSheetState()
906 return $this->sheetState;
916 public function setSheetState($value)
918 $this->sheetState = $value;
928 public function getPageSetup()
930 return $this->pageSetup;
938 public function setPageSetup(PageSetup $pValue)
940 $this->pageSetup = $pValue;
950 public function getPageMargins()
952 return $this->pageMargins;
960 public function setPageMargins(PageMargins $pValue)
962 $this->pageMargins = $pValue;
972 public function getHeaderFooter()
974 return $this->headerFooter;
982 public function setHeaderFooter(HeaderFooter $pValue)
984 $this->headerFooter = $pValue;
994 public function getSheetView()
996 return $this->sheetView;
1004 public function setSheetView(SheetView $pValue)
1006 $this->sheetView = $pValue;
1016 public function getProtection()
1018 return $this->protection;
1026 public function setProtection(Protection $pValue)
1028 $this->protection = $pValue;
1029 $this->dirty =
true;
1042 public function getHighestColumn(
$row =
null)
1048 return $this->getHighestDataColumn(
$row);
1059 public function getHighestDataColumn(
$row =
null)
1061 return $this->cellCollection->getHighestColumn(
$row);
1072 public function getHighestRow($column =
null)
1074 if ($column ==
null) {
1075 return $this->cachedHighestRow;
1078 return $this->getHighestDataRow($column);
1089 public function getHighestDataRow($column =
null)
1091 return $this->cellCollection->getHighestRow($column);
1099 public function getHighestRowAndColumn()
1101 return $this->cellCollection->getHighestRowAndColumn();
1112 public function setCellValue($pCoordinate, $pValue)
1114 $this->getCell($pCoordinate)->setValue($pValue);
1128 public function setCellValueByColumnAndRow($columnIndex,
$row, $value)
1130 $this->getCellByColumnAndRow($columnIndex,
$row)->setValue($value);
1144 public function setCellValueExplicit($pCoordinate, $pValue, $pDataType)
1147 $this->getCell($pCoordinate)->setValueExplicit($pValue, $pDataType);
1162 public function setCellValueExplicitByColumnAndRow($columnIndex,
$row, $value, $dataType)
1164 $this->getCellByColumnAndRow($columnIndex,
$row)->setValueExplicit($value, $dataType);
1176 public function getCell(
string $coordinate): Cell
1179 if ($this->cellCollection->has($coordinate)) {
1181 $cell = $this->cellCollection->get($coordinate);
1187 [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($coordinate);
1188 $cell = $sheet->cellCollection->get($finalCoordinate);
1190 return $cell ?? $sheet->createNewCell($finalCoordinate);
1199 private function getWorksheetAndCoordinate(
string $pCoordinate): array
1202 $finalCoordinate =
null;
1205 if (strpos($pCoordinate,
'!') !==
false) {
1206 $worksheetReference = self::extractSheetTitle($pCoordinate,
true);
1208 $sheet = $this->parent->getSheetByName($worksheetReference[0]);
1209 $finalCoordinate = strtoupper($worksheetReference[1]);
1212 throw new Exception(
'Sheet not found for name: ' . $worksheetReference[0]);
1219 $namedRange = $this->validateNamedRange($pCoordinate,
true);
1220 if ($namedRange !==
null) {
1221 $sheet = $namedRange->getWorksheet();
1223 throw new Exception(
'Sheet not found for named range: ' . $namedRange->getName());
1226 $cellCoordinate = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(),
'!')),
'!');
1227 $finalCoordinate = str_replace(
'$',
'', $cellCoordinate);
1231 if (!$sheet || !$finalCoordinate) {
1233 $finalCoordinate = strtoupper($pCoordinate);
1237 throw new Exception(
'Cell coordinate string can not be a range of cells.');
1238 } elseif (strpos($finalCoordinate,
'$') !==
false) {
1239 throw new Exception(
'Cell coordinate must not be absolute.');
1242 return [$sheet, $finalCoordinate];
1252 private function getCellOrNull($coordinate): ?Cell
1255 if ($this->cellCollection->has($coordinate)) {
1256 return $this->cellCollection->get($coordinate);
1270 public function getCellByColumnAndRow($columnIndex,
$row): Cell
1273 $coordinate = $columnLetter .
$row;
1275 if ($this->cellCollection->has($coordinate)) {
1277 $cell = $this->cellCollection->get($coordinate);
1283 return $this->createNewCell($coordinate);
1293 private function createNewCell($pCoordinate)
1296 $this->cellCollection->add($pCoordinate, $cell);
1297 $this->cellCollectionIsSorted =
false;
1302 if ($this->cachedHighestColumn < $aIndexes[0]) {
1303 $this->cachedHighestColumn = $aIndexes[0];
1305 if ($aIndexes[1] > $this->cachedHighestRow) {
1306 $this->cachedHighestRow = $aIndexes[1];
1311 $rowDimension = $this->rowDimensions[
$row] ??
null;
1312 $columnDimension = $this->columnDimensions[$column] ??
null;
1314 if ($rowDimension !==
null && $rowDimension->getXfIndex() > 0) {
1316 $cell->setXfIndex($rowDimension->getXfIndex());
1317 } elseif ($columnDimension !==
null && $columnDimension->getXfIndex() > 0) {
1319 $cell->setXfIndex($columnDimension->getXfIndex());
1332 public function cellExists($coordinate)
1335 [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($coordinate);
1337 return $sheet->cellCollection->has($finalCoordinate);
1348 public function cellExistsByColumnAndRow($columnIndex,
$row)
1358 public function getRowDimension(
int $pRow): RowDimension
1361 if (!isset($this->rowDimensions[$pRow])) {
1362 $this->rowDimensions[$pRow] =
new RowDimension($pRow);
1364 $this->cachedHighestRow = max($this->cachedHighestRow, $pRow);
1367 return $this->rowDimensions[$pRow];
1375 public function getColumnDimension(
string $pColumn): ColumnDimension
1378 $pColumn = strtoupper($pColumn);
1381 if (!isset($this->columnDimensions[$pColumn])) {
1382 $this->columnDimensions[$pColumn] =
new ColumnDimension($pColumn);
1385 if ($this->cachedHighestColumn < $columnIndex) {
1386 $this->cachedHighestColumn = $columnIndex;
1390 return $this->columnDimensions[$pColumn];
1398 public function getColumnDimensionByColumn(
int $columnIndex): ColumnDimension
1408 public function getStyles()
1410 return $this->styles;
1420 public function getStyle($pCellCoordinate)
1423 $this->parent->setActiveSheetIndex($this->parent->getIndex($this));
1426 $this->setSelectedCells($pCellCoordinate);
1428 return $this->parent->getCellXfSupervisor();
1438 public function getConditionalStyles($pCoordinate)
1440 $pCoordinate = strtoupper($pCoordinate);
1441 if (!isset($this->conditionalStylesCollection[$pCoordinate])) {
1442 $this->conditionalStylesCollection[$pCoordinate] = [];
1445 return $this->conditionalStylesCollection[$pCoordinate];
1455 public function conditionalStylesExists($pCoordinate)
1457 return isset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
1467 public function removeConditionalStyles($pCoordinate)
1469 unset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
1479 public function getConditionalStylesCollection()
1481 return $this->conditionalStylesCollection;
1492 public function setConditionalStyles($pCoordinate, $pValue)
1494 $this->conditionalStylesCollection[strtoupper($pCoordinate)] = $pValue;
1509 public function getStyleByColumnAndRow($columnIndex1, $row1, $columnIndex2 =
null, $row2 =
null)
1511 if ($columnIndex2 !==
null && $row2 !==
null) {
1514 return $this->getStyle($cellRange);
1530 public function duplicateStyle(Style $pCellStyle, $pRange)
1533 $workbook = $this->parent;
1534 if ($existingStyle = $this->parent->getCellXfByHashCode($pCellStyle->getHashCode())) {
1536 $xfIndex = $existingStyle->getIndex();
1539 $workbook->addCellXf($pCellStyle);
1540 $xfIndex = $pCellStyle->getIndex();
1547 if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
1549 $rangeStart = $rangeEnd;
1554 for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
1555 for (
$row = $rangeStart[1];
$row <= $rangeEnd[1]; ++
$row) {
1573 public function duplicateConditionalStyle(array $pCellStyle, $pRange =
'')
1575 foreach ($pCellStyle as $cellStyle) {
1576 if (!($cellStyle instanceof Conditional)) {
1577 throw new Exception(
'Style is not a conditional style');
1585 if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
1587 $rangeStart = $rangeEnd;
1592 for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
1593 for (
$row = $rangeStart[1];
$row <= $rangeEnd[1]; ++
$row) {
1609 public function setBreak($pCoordinate, $pBreak)
1612 $pCoordinate = strtoupper($pCoordinate);
1614 if ($pCoordinate !=
'') {
1615 if ($pBreak == self::BREAK_NONE) {
1616 if (isset($this->breaks[$pCoordinate])) {
1617 unset($this->breaks[$pCoordinate]);
1620 $this->breaks[$pCoordinate] = $pBreak;
1623 throw new Exception(
'No cell coordinate specified.');
1638 public function setBreakByColumnAndRow($columnIndex,
$row, $break)
1648 public function getBreaks()
1650 return $this->breaks;
1660 public function mergeCells($pRange)
1663 $pRange = strtoupper($pRange);
1665 if (strpos($pRange,
':') !==
false) {
1666 $this->mergeCells[$pRange] = $pRange;
1674 $upperLeft = $aReferences[0];
1675 if (!$this->cellExists($upperLeft)) {
1680 $count = count($aReferences);
1681 for (
$i = 1;
$i < $count; ++
$i) {
1682 if ($this->cellExists($aReferences[
$i])) {
1687 throw new Exception(
'Merge must be set on a range of cells.');
1703 public function mergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
1707 return $this->mergeCells($cellRange);
1717 public function unmergeCells($pRange)
1720 $pRange = strtoupper($pRange);
1722 if (strpos($pRange,
':') !==
false) {
1723 if (isset($this->mergeCells[$pRange])) {
1724 unset($this->mergeCells[$pRange]);
1726 throw new Exception(
'Cell range ' . $pRange .
' not known as merged.');
1729 throw new Exception(
'Merge can only be removed from a range of cells.');
1745 public function unmergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
1749 return $this->unmergeCells($cellRange);
1757 public function getMergeCells()
1759 return $this->mergeCells;
1770 public function setMergeCells(array $pValue)
1772 $this->mergeCells = $pValue;
1786 public function protectCells($pRange, $pPassword, $pAlreadyHashed =
false)
1789 $pRange = strtoupper($pRange);
1791 if (!$pAlreadyHashed) {
1794 $this->protectedCells[$pRange] = $pPassword;
1811 public function protectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2,
$password, $alreadyHashed =
false)
1815 return $this->protectCells($cellRange,
$password, $alreadyHashed);
1825 public function unprotectCells($pRange)
1828 $pRange = strtoupper($pRange);
1830 if (isset($this->protectedCells[$pRange])) {
1831 unset($this->protectedCells[$pRange]);
1833 throw new Exception(
'Cell range ' . $pRange .
' not known as protected.');
1849 public function unprotectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
1853 return $this->unprotectCells($cellRange);
1861 public function getProtectedCells()
1863 return $this->protectedCells;
1871 public function getAutoFilter()
1873 return $this->autoFilter;
1884 public function setAutoFilter($pValue)
1886 if (is_string($pValue)) {
1887 $this->autoFilter->setRange($pValue);
1888 } elseif (is_object($pValue) && ($pValue instanceof AutoFilter)) {
1889 $this->autoFilter = $pValue;
1905 public function setAutoFilterByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
1907 return $this->setAutoFilter(
1919 public function removeAutoFilter()
1921 $this->autoFilter->setRange(
null);
1931 public function getFreezePane()
1933 return $this->freezePane;
1950 public function freezePane($cell, $topLeftCell =
null)
1953 throw new Exception(
'Freeze pane can not be set on a range of cells.');
1956 if ($cell !==
null && $topLeftCell ===
null) {
1958 $topLeftCell = $coordinate[0] . $coordinate[1];
1961 $this->freezePane = $cell;
1962 $this->topLeftCell = $topLeftCell;
1975 public function freezePaneByColumnAndRow($columnIndex,
$row)
1985 public function unfreezePane()
1987 return $this->freezePane(
null);
1995 public function getTopLeftCell()
1997 return $this->topLeftCell;
2008 public function insertNewRowBefore($pBefore, $pNumRows = 1)
2010 if ($pBefore >= 1) {
2012 $objReferenceHelper->insertNewBefore(
'A' . $pBefore, 0, $pNumRows, $this);
2014 throw new Exception(
'Rows can only be inserted before at least row 1.');
2028 public function insertNewColumnBefore($pBefore, $pNumCols = 1)
2030 if (!is_numeric($pBefore)) {
2032 $objReferenceHelper->insertNewBefore($pBefore .
'1', $pNumCols, 0, $this);
2034 throw new Exception(
'Column references should not be numeric.');
2048 public function insertNewColumnBeforeByIndex($beforeColumnIndex, $pNumCols = 1)
2050 if ($beforeColumnIndex >= 1) {
2054 throw new Exception(
'Columns can only be inserted before at least column A (1).');
2065 public function removeRow($pRow, $pNumRows = 1)
2068 throw new Exception(
'Rows to be deleted should at least start from row 1.');
2071 $highestRow = $this->getHighestDataRow();
2072 $removedRowsCounter = 0;
2074 for (
$r = 0;
$r < $pNumRows; ++
$r) {
2075 if ($pRow +
$r <= $highestRow) {
2076 $this->getCellCollection()->removeRow($pRow +
$r);
2077 ++$removedRowsCounter;
2082 $objReferenceHelper->insertNewBefore(
'A' . ($pRow + $pNumRows), 0, -$pNumRows, $this);
2083 for (
$r = 0;
$r < $removedRowsCounter; ++
$r) {
2084 $this->getCellCollection()->removeRow($highestRow);
2099 public function removeColumn($pColumn, $pNumCols = 1)
2101 if (is_numeric($pColumn)) {
2102 throw new Exception(
'Column references should not be numeric.');
2105 $highestColumn = $this->getHighestDataColumn();
2109 if ($pColumnIndex > $highestColumnIndex) {
2115 $objReferenceHelper->insertNewBefore($pColumn .
'1', -$pNumCols, 0, $this);
2117 $maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1;
2119 for (
$c = 0,
$n = min($maxPossibleColumnsToBeRemoved, $pNumCols);
$c <
$n; ++
$c) {
2120 $this->getCellCollection()->removeColumn($highestColumn);
2124 $this->garbageCollect();
2137 public function removeColumnByIndex($columnIndex, $numColumns = 1)
2139 if ($columnIndex >= 1) {
2143 throw new Exception(
'Columns to be deleted should at least start from column A (1)');
2151 public function getShowGridlines()
2153 return $this->showGridlines;
2163 public function setShowGridlines($pValue)
2165 $this->showGridlines = $pValue;
2175 public function getPrintGridlines()
2177 return $this->printGridlines;
2187 public function setPrintGridlines($pValue)
2189 $this->printGridlines = $pValue;
2199 public function getShowRowColHeaders()
2201 return $this->showRowColHeaders;
2211 public function setShowRowColHeaders($pValue)
2213 $this->showRowColHeaders = $pValue;
2223 public function getShowSummaryBelow()
2225 return $this->showSummaryBelow;
2235 public function setShowSummaryBelow($pValue)
2237 $this->showSummaryBelow = $pValue;
2247 public function getShowSummaryRight()
2249 return $this->showSummaryRight;
2259 public function setShowSummaryRight($pValue)
2261 $this->showSummaryRight = $pValue;
2271 public function getComments()
2273 return $this->comments;
2283 public function setComments(array $pValue)
2285 $this->comments = $pValue;
2297 public function getComment($pCellCoordinate)
2300 $pCellCoordinate = strtoupper($pCellCoordinate);
2303 throw new Exception(
'Cell coordinate string can not be a range of cells.');
2304 } elseif (strpos($pCellCoordinate,
'$') !==
false) {
2305 throw new Exception(
'Cell coordinate string must not be absolute.');
2306 } elseif ($pCellCoordinate ==
'') {
2307 throw new Exception(
'Cell coordinate can not be zero-length string.');
2311 if (isset($this->comments[$pCellCoordinate])) {
2312 return $this->comments[$pCellCoordinate];
2316 $newComment =
new Comment();
2317 $this->comments[$pCellCoordinate] = $newComment;
2330 public function getCommentByColumnAndRow($columnIndex,
$row)
2340 public function getActiveCell()
2342 return $this->activeCell;
2350 public function getSelectedCells()
2352 return $this->selectedCells;
2362 public function setSelectedCell($pCoordinate)
2364 return $this->setSelectedCells($pCoordinate);
2374 public function setSelectedCells($pCoordinate)
2377 $pCoordinate = strtoupper($pCoordinate);
2380 $pCoordinate = preg_replace(
'/^([A-Z]+)$/',
'${1}:${1}', $pCoordinate);
2383 $pCoordinate = preg_replace(
'/^(\d+)$/',
'${1}:${1}', $pCoordinate);
2386 $pCoordinate = preg_replace(
'/^([A-Z]+):([A-Z]+)$/',
'${1}1:${2}1048576', $pCoordinate);
2389 $pCoordinate = preg_replace(
'/^(\d+):(\d+)$/',
'A${1}:XFD${2}', $pCoordinate);
2393 $this->activeCell = $first[0];
2395 $this->activeCell = $pCoordinate;
2397 $this->selectedCells = $pCoordinate;
2410 public function setSelectedCellByColumnAndRow($columnIndex,
$row)
2420 public function getRightToLeft()
2422 return $this->rightToLeft;
2432 public function setRightToLeft($value)
2434 $this->rightToLeft = $value;
2449 public function fromArray(array
$source, $nullValue =
null, $startCell =
'A1', $strictNullComparison =
false)
2452 if (!is_array(end(
$source))) {
2460 foreach (
$source as $rowData) {
2461 $currentColumn = $startColumn;
2462 foreach ($rowData as $cellValue) {
2463 if ($strictNullComparison) {
2464 if ($cellValue !== $nullValue) {
2466 $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2469 if ($cellValue != $nullValue) {
2471 $this->getCell($currentColumn . $startRow)->setValue($cellValue);
2494 public function rangeToArray($pRange, $nullValue =
null, $calculateFormulas =
true, $formatData =
true, $returnCellRef =
false)
2501 $minRow = $rangeStart[1];
2503 $maxRow = $rangeEnd[1];
2509 $rRef = $returnCellRef ?
$row : ++
$r;
2512 for ($col = $minCol; $col != $maxCol; ++$col) {
2513 $cRef = $returnCellRef ? $col : ++
$c;
2516 if ($this->cellCollection->has($col .
$row)) {
2518 $cell = $this->cellCollection->get($col .
$row);
2519 if ($cell->getValue() !==
null) {
2520 if ($cell->getValue() instanceof RichText) {
2521 $returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
2523 if ($calculateFormulas) {
2524 $returnValue[$rRef][$cRef] = $cell->getCalculatedValue();
2526 $returnValue[$rRef][$cRef] = $cell->getValue();
2531 $style = $this->parent->getCellXfByIndex($cell->getXfIndex());
2533 $returnValue[$rRef][$cRef],
2539 $returnValue[$rRef][$cRef] = $nullValue;
2543 $returnValue[$rRef][$cRef] = $nullValue;
2549 return $returnValue;
2552 private function validateNamedRange(
string $definedName,
bool $returnNullIfInvalid =
false): ?DefinedName
2555 if ($namedRange ===
null) {
2556 if ($returnNullIfInvalid) {
2560 throw new Exception(
'Named Range ' . $definedName .
' does not exist.');
2563 if ($namedRange->isFormula()) {
2564 if ($returnNullIfInvalid) {
2568 throw new Exception(
'Defined Named ' . $definedName .
' is a formula, not a range or cell.');
2571 if ($namedRange->getLocalOnly() && $this->getHashCode() !== $namedRange->getWorksheet()->getHashCode()) {
2572 if ($returnNullIfInvalid) {
2576 throw new Exception(
2577 'Named range ' . $definedName .
' is not accessible from within sheet ' . $this->getTitle()
2596 public function namedRangeToArray(
string $definedName, $nullValue =
null, $calculateFormulas =
true, $formatData =
true, $returnCellRef =
false)
2598 $namedRange = $this->validateNamedRange($definedName);
2599 $workSheet = $namedRange->getWorksheet();
2600 $cellRange = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(),
'!')),
'!');
2601 $cellRange = str_replace(
'$',
'', $cellRange);
2603 return $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2617 public function toArray($nullValue =
null, $calculateFormulas =
true, $formatData =
true, $returnCellRef =
false)
2620 $this->garbageCollect();
2623 $maxCol = $this->getHighestColumn();
2624 $maxRow = $this->getHighestRow();
2627 return $this->rangeToArray(
'A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
2638 public function getRowIterator($startRow = 1, $endRow =
null)
2640 return new RowIterator($this, $startRow, $endRow);
2651 public function getColumnIterator($startColumn =
'A', $endColumn =
null)
2653 return new ColumnIterator($this, $startColumn, $endColumn);
2661 public function garbageCollect()
2664 $this->cellCollection->get(
'A1');
2667 $colRow = $this->cellCollection->getHighestRowAndColumn();
2668 $highestRow = $colRow[
'row'];
2672 foreach ($this->columnDimensions as $dimension) {
2677 foreach ($this->rowDimensions as $dimension) {
2678 $highestRow = max($highestRow, $dimension->getRowIndex());
2682 if ($highestColumn < 1) {
2683 $this->cachedHighestColumn = 1;
2685 $this->cachedHighestColumn = $highestColumn;
2687 $this->cachedHighestRow = $highestRow;
2701 $this->
hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ?
't' :
'f') . __CLASS__);
2702 $this->dirty =
false;
2719 public static function extractSheetTitle($pRange, $returnRange =
false)
2722 if (($sep = strrpos($pRange,
'!')) ===
false) {
2723 return $returnRange ? [
'', $pRange] :
'';
2727 return [substr($pRange, 0, $sep), substr($pRange, $sep + 1)];
2730 return substr($pRange, $sep + 1);
2740 public function getHyperlink($pCellCoordinate)
2743 if (isset($this->hyperlinkCollection[$pCellCoordinate])) {
2744 return $this->hyperlinkCollection[$pCellCoordinate];
2748 $this->hyperlinkCollection[$pCellCoordinate] =
new Hyperlink();
2750 return $this->hyperlinkCollection[$pCellCoordinate];
2760 public function setHyperlink($pCellCoordinate, ?Hyperlink $pHyperlink =
null)
2762 if ($pHyperlink ===
null) {
2763 unset($this->hyperlinkCollection[$pCellCoordinate]);
2765 $this->hyperlinkCollection[$pCellCoordinate] = $pHyperlink;
2778 public function hyperlinkExists($pCoordinate)
2780 return isset($this->hyperlinkCollection[$pCoordinate]);
2788 public function getHyperlinkCollection()
2790 return $this->hyperlinkCollection;
2800 public function getDataValidation($pCellCoordinate)
2803 if (isset($this->dataValidationCollection[$pCellCoordinate])) {
2804 return $this->dataValidationCollection[$pCellCoordinate];
2808 $this->dataValidationCollection[$pCellCoordinate] =
new DataValidation();
2810 return $this->dataValidationCollection[$pCellCoordinate];
2820 public function setDataValidation($pCellCoordinate, ?DataValidation $pDataValidation =
null)
2822 if ($pDataValidation ===
null) {
2823 unset($this->dataValidationCollection[$pCellCoordinate]);
2825 $this->dataValidationCollection[$pCellCoordinate] = $pDataValidation;
2838 public function dataValidationExists($pCoordinate)
2840 return isset($this->dataValidationCollection[$pCoordinate]);
2848 public function getDataValidationCollection()
2850 return $this->dataValidationCollection;
2860 public function shrinkRangeToFit($range)
2862 $maxCol = $this->getHighestColumn();
2863 $maxRow = $this->getHighestRow();
2866 $rangeBlocks = explode(
' ', $range);
2867 foreach ($rangeBlocks as &$rangeSet) {
2873 if ($rangeBoundaries[0][1] > $maxRow) {
2874 $rangeBoundaries[0][1] = $maxRow;
2879 if ($rangeBoundaries[1][1] > $maxRow) {
2880 $rangeBoundaries[1][1] = $maxRow;
2882 $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] .
':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1];
2886 return implode(
' ', $rangeBlocks);
2894 public function getTabColor()
2896 if ($this->tabColor ===
null) {
2897 $this->tabColor =
new Color();
2900 return $this->tabColor;
2908 public function resetTabColor()
2910 $this->tabColor =
null;
2920 public function isTabColorSet()
2922 return $this->tabColor !==
null;
2930 public function copy()
2938 public function __clone()
2941 foreach ($this as
$key => $val) {
2942 if (
$key ==
'parent') {
2946 if (is_object($val) || (is_array($val))) {
2947 if (
$key ==
'cellCollection') {
2948 $newCollection = $this->cellCollection->cloneCellCollection($this);
2949 $this->cellCollection = $newCollection;
2950 } elseif (
$key ==
'drawingCollection') {
2951 $currentCollection = $this->drawingCollection;
2952 $this->drawingCollection =
new ArrayObject();
2953 foreach ($currentCollection as $item) {
2954 if (is_object($item)) {
2955 $newDrawing = clone $item;
2956 $newDrawing->setWorksheet($this);
2959 } elseif ((
$key ==
'autoFilter') && ($this->autoFilter instanceof AutoFilter)) {
2960 $newAutoFilter = clone $this->autoFilter;
2961 $this->autoFilter = $newAutoFilter;
2962 $this->autoFilter->setParent($this);
2964 $this->{
$key} = unserialize(serialize($val));
2980 public function setCodeName($pValue, $validate =
true)
2983 if ($this->getCodeName() == $pValue) {
2988 $pValue = str_replace(
' ',
'_', $pValue);
2992 self::checkSheetCodeName($pValue);
2996 if ($this->getParent()) {
2998 if ($this->getParent()->sheetCodeNameExists($pValue)) {
3005 while ($this->getParent()->sheetCodeNameExists($pValue .
'_' .
$i)) {
3011 } elseif (
$i == 100) {
3018 $pValue .=
'_' .
$i;
3023 $this->codeName = $pValue;
3033 public function getCodeName()
3035 return $this->codeName;
3043 public function hasCodeName()
3045 return $this->codeName !==
null;
An exception for terminatinating execution or to throw for unit testing.
const CALCULATION_REGEXP_DEFINEDNAME
const CALCULATION_REGEXP_CELLREF
static getInstance(?Spreadsheet $spreadsheet=null)
Get an instance of this class.
Helper class to manipulate cell coordinates.
static getRangeBoundaries($pRange)
Calculate range boundaries.
static coordinateFromString($pCoordinateString)
Coordinate from string.
static coordinateIsRange($coord)
Checks if a coordinate represents a range of cells.
static indexesFromString(string $coordinates)
Get indexes from a string coordinates.
static columnIndexFromString($pString)
Column index from string.
static stringFromColumnIndex($columnIndex)
String from column index.
static splitRange($pRange)
Split range into coordinate strings.
static rangeDimension($pRange)
Calculate range dimension.
static rangeBoundaries($pRange)
Calculate range boundaries.
static extractAllCellReferencesInRange($cellRange)
Extract all cell references in range, which may be comprised of multiple cell ranges.
static getInstance(Worksheet $parent)
Initialise the cache storage.
static resolveName(string $pDefinedName, Worksheet $pSheet, string $sheetName='')
Resolve a named range to a regular cell range or formula.
static getInstance()
Get an instance of this class.
static calculateColumnWidth(\PhpOffice\PhpSpreadsheet\Style\Font $font, $cellText='', $rotation=0, ?\PhpOffice\PhpSpreadsheet\Style\Font $defaultFont=null)
Calculate an (approximate) OpenXML column width, based on font size and text contained.
static hashPassword(string $password, string $algorithm='', string $salt='', int $spinCount=10000)
Create a password hash from a given string by a specific algorithm.
static countCharacters($value, $enc='UTF-8')
Get character count.
static substring($pValue, $pStart, $pLength=0)
Get a substring of a UTF-8 encoded string.
Paper size taken from Office Open XML Part 4 - Markup Language Reference, page 1988:.
const SHEETSTATE_VERYHIDDEN
const SHEET_TITLE_MAXIMUM_LENGTH
getHashCode()
Get hash code.
hash(StreamInterface $stream, $algo, $rawOutput=false)
Calculate a hash of a Stream.
toArray($value)
Wrap the given value in an array if it is no array.