vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php line 1273

Open in your IDE?
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Worksheet;
  3. use ArrayObject;
  4. use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
  5. use PhpOffice\PhpSpreadsheet\Calculation\Functions;
  6. use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
  7. use PhpOffice\PhpSpreadsheet\Cell\Cell;
  8. use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
  9. use PhpOffice\PhpSpreadsheet\Cell\CellRange;
  10. use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
  11. use PhpOffice\PhpSpreadsheet\Cell\DataType;
  12. use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
  13. use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
  14. use PhpOffice\PhpSpreadsheet\Cell\IValueBinder;
  15. use PhpOffice\PhpSpreadsheet\Chart\Chart;
  16. use PhpOffice\PhpSpreadsheet\Collection\Cells;
  17. use PhpOffice\PhpSpreadsheet\Collection\CellsFactory;
  18. use PhpOffice\PhpSpreadsheet\Comment;
  19. use PhpOffice\PhpSpreadsheet\DefinedName;
  20. use PhpOffice\PhpSpreadsheet\Exception;
  21. use PhpOffice\PhpSpreadsheet\IComparable;
  22. use PhpOffice\PhpSpreadsheet\ReferenceHelper;
  23. use PhpOffice\PhpSpreadsheet\RichText\RichText;
  24. use PhpOffice\PhpSpreadsheet\Shared;
  25. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  26. use PhpOffice\PhpSpreadsheet\Style\Alignment;
  27. use PhpOffice\PhpSpreadsheet\Style\Color;
  28. use PhpOffice\PhpSpreadsheet\Style\Conditional;
  29. use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
  30. use PhpOffice\PhpSpreadsheet\Style\Style;
  31. class Worksheet implements IComparable
  32. {
  33.     // Break types
  34.     public const BREAK_NONE 0;
  35.     public const BREAK_ROW 1;
  36.     public const BREAK_COLUMN 2;
  37.     // Maximum column for row break
  38.     public const BREAK_ROW_MAX_COLUMN 16383;
  39.     // Sheet state
  40.     public const SHEETSTATE_VISIBLE 'visible';
  41.     public const SHEETSTATE_HIDDEN 'hidden';
  42.     public const SHEETSTATE_VERYHIDDEN 'veryHidden';
  43.     public const MERGE_CELL_CONTENT_EMPTY 'empty';
  44.     public const MERGE_CELL_CONTENT_HIDE 'hide';
  45.     public const MERGE_CELL_CONTENT_MERGE 'merge';
  46.     protected const SHEET_NAME_REQUIRES_NO_QUOTES '/^[_\p{L}][_\p{L}\p{N}]*$/mui';
  47.     /**
  48.      * Maximum 31 characters allowed for sheet title.
  49.      *
  50.      * @var int
  51.      */
  52.     const SHEET_TITLE_MAXIMUM_LENGTH 31;
  53.     /**
  54.      * Invalid characters in sheet title.
  55.      *
  56.      * @var array
  57.      */
  58.     private static $invalidCharacters = ['*'':''/''\\''?''['']'];
  59.     /**
  60.      * Parent spreadsheet.
  61.      *
  62.      * @var ?Spreadsheet
  63.      */
  64.     private $parent;
  65.     /**
  66.      * Collection of cells.
  67.      *
  68.      * @var Cells
  69.      */
  70.     private $cellCollection;
  71.     /**
  72.      * Collection of row dimensions.
  73.      *
  74.      * @var RowDimension[]
  75.      */
  76.     private $rowDimensions = [];
  77.     /**
  78.      * Default row dimension.
  79.      *
  80.      * @var RowDimension
  81.      */
  82.     private $defaultRowDimension;
  83.     /**
  84.      * Collection of column dimensions.
  85.      *
  86.      * @var ColumnDimension[]
  87.      */
  88.     private $columnDimensions = [];
  89.     /**
  90.      * Default column dimension.
  91.      *
  92.      * @var ColumnDimension
  93.      */
  94.     private $defaultColumnDimension;
  95.     /**
  96.      * Collection of drawings.
  97.      *
  98.      * @var ArrayObject<int, BaseDrawing>
  99.      */
  100.     private $drawingCollection;
  101.     /**
  102.      * Collection of Chart objects.
  103.      *
  104.      * @var ArrayObject<int, Chart>
  105.      */
  106.     private $chartCollection;
  107.     /**
  108.      * Collection of Table objects.
  109.      *
  110.      * @var ArrayObject<int, Table>
  111.      */
  112.     private $tableCollection;
  113.     /**
  114.      * Worksheet title.
  115.      *
  116.      * @var string
  117.      */
  118.     private $title;
  119.     /**
  120.      * Sheet state.
  121.      *
  122.      * @var string
  123.      */
  124.     private $sheetState;
  125.     /**
  126.      * Page setup.
  127.      *
  128.      * @var PageSetup
  129.      */
  130.     private $pageSetup;
  131.     /**
  132.      * Page margins.
  133.      *
  134.      * @var PageMargins
  135.      */
  136.     private $pageMargins;
  137.     /**
  138.      * Page header/footer.
  139.      *
  140.      * @var HeaderFooter
  141.      */
  142.     private $headerFooter;
  143.     /**
  144.      * Sheet view.
  145.      *
  146.      * @var SheetView
  147.      */
  148.     private $sheetView;
  149.     /**
  150.      * Protection.
  151.      *
  152.      * @var Protection
  153.      */
  154.     private $protection;
  155.     /**
  156.      * Collection of styles.
  157.      *
  158.      * @var Style[]
  159.      */
  160.     private $styles = [];
  161.     /**
  162.      * Conditional styles. Indexed by cell coordinate, e.g. 'A1'.
  163.      *
  164.      * @var array
  165.      */
  166.     private $conditionalStylesCollection = [];
  167.     /**
  168.      * Collection of row breaks.
  169.      *
  170.      * @var PageBreak[]
  171.      */
  172.     private $rowBreaks = [];
  173.     /**
  174.      * Collection of column breaks.
  175.      *
  176.      * @var PageBreak[]
  177.      */
  178.     private $columnBreaks = [];
  179.     /**
  180.      * Collection of merged cell ranges.
  181.      *
  182.      * @var string[]
  183.      */
  184.     private $mergeCells = [];
  185.     /**
  186.      * Collection of protected cell ranges.
  187.      *
  188.      * @var string[]
  189.      */
  190.     private $protectedCells = [];
  191.     /**
  192.      * Autofilter Range and selection.
  193.      *
  194.      * @var AutoFilter
  195.      */
  196.     private $autoFilter;
  197.     /**
  198.      * Freeze pane.
  199.      *
  200.      * @var null|string
  201.      */
  202.     private $freezePane;
  203.     /**
  204.      * Default position of the right bottom pane.
  205.      *
  206.      * @var null|string
  207.      */
  208.     private $topLeftCell;
  209.     /**
  210.      * Show gridlines?
  211.      *
  212.      * @var bool
  213.      */
  214.     private $showGridlines true;
  215.     /**
  216.      * Print gridlines?
  217.      *
  218.      * @var bool
  219.      */
  220.     private $printGridlines false;
  221.     /**
  222.      * Show row and column headers?
  223.      *
  224.      * @var bool
  225.      */
  226.     private $showRowColHeaders true;
  227.     /**
  228.      * Show summary below? (Row/Column outline).
  229.      *
  230.      * @var bool
  231.      */
  232.     private $showSummaryBelow true;
  233.     /**
  234.      * Show summary right? (Row/Column outline).
  235.      *
  236.      * @var bool
  237.      */
  238.     private $showSummaryRight true;
  239.     /**
  240.      * Collection of comments.
  241.      *
  242.      * @var Comment[]
  243.      */
  244.     private $comments = [];
  245.     /**
  246.      * Active cell. (Only one!).
  247.      *
  248.      * @var string
  249.      */
  250.     private $activeCell 'A1';
  251.     /**
  252.      * Selected cells.
  253.      *
  254.      * @var string
  255.      */
  256.     private $selectedCells 'A1';
  257.     /**
  258.      * Cached highest column.
  259.      *
  260.      * @var int
  261.      */
  262.     private $cachedHighestColumn 1;
  263.     /**
  264.      * Cached highest row.
  265.      *
  266.      * @var int
  267.      */
  268.     private $cachedHighestRow 1;
  269.     /**
  270.      * Right-to-left?
  271.      *
  272.      * @var bool
  273.      */
  274.     private $rightToLeft false;
  275.     /**
  276.      * Hyperlinks. Indexed by cell coordinate, e.g. 'A1'.
  277.      *
  278.      * @var array
  279.      */
  280.     private $hyperlinkCollection = [];
  281.     /**
  282.      * Data validation objects. Indexed by cell coordinate, e.g. 'A1'.
  283.      *
  284.      * @var array
  285.      */
  286.     private $dataValidationCollection = [];
  287.     /**
  288.      * Tab color.
  289.      *
  290.      * @var null|Color
  291.      */
  292.     private $tabColor;
  293.     /**
  294.      * Dirty flag.
  295.      *
  296.      * @var bool
  297.      */
  298.     private $dirty true;
  299.     /**
  300.      * Hash.
  301.      *
  302.      * @var string
  303.      */
  304.     private $hash;
  305.     /**
  306.      * CodeName.
  307.      *
  308.      * @var string
  309.      */
  310.     private $codeName;
  311.     /**
  312.      * Create a new worksheet.
  313.      *
  314.      * @param string $title
  315.      */
  316.     public function __construct(?Spreadsheet $parent null$title 'Worksheet')
  317.     {
  318.         // Set parent and title
  319.         $this->parent $parent;
  320.         $this->setTitle($titlefalse);
  321.         // setTitle can change $pTitle
  322.         $this->setCodeName($this->getTitle());
  323.         $this->setSheetState(self::SHEETSTATE_VISIBLE);
  324.         $this->cellCollection CellsFactory::getInstance($this);
  325.         // Set page setup
  326.         $this->pageSetup = new PageSetup();
  327.         // Set page margins
  328.         $this->pageMargins = new PageMargins();
  329.         // Set page header/footer
  330.         $this->headerFooter = new HeaderFooter();
  331.         // Set sheet view
  332.         $this->sheetView = new SheetView();
  333.         // Drawing collection
  334.         $this->drawingCollection = new ArrayObject();
  335.         // Chart collection
  336.         $this->chartCollection = new ArrayObject();
  337.         // Protection
  338.         $this->protection = new Protection();
  339.         // Default row dimension
  340.         $this->defaultRowDimension = new RowDimension(null);
  341.         // Default column dimension
  342.         $this->defaultColumnDimension = new ColumnDimension(null);
  343.         // AutoFilter
  344.         $this->autoFilter = new AutoFilter(''$this);
  345.         // Table collection
  346.         $this->tableCollection = new ArrayObject();
  347.     }
  348.     /**
  349.      * Disconnect all cells from this Worksheet object,
  350.      * typically so that the worksheet object can be unset.
  351.      */
  352.     public function disconnectCells(): void
  353.     {
  354.         if ($this->cellCollection !== null) {
  355.             $this->cellCollection->unsetWorksheetCells();
  356.             // @phpstan-ignore-next-line
  357.             $this->cellCollection null;
  358.         }
  359.         //    detach ourself from the workbook, so that it can then delete this worksheet successfully
  360.         $this->parent null;
  361.     }
  362.     /**
  363.      * Code to execute when this worksheet is unset().
  364.      */
  365.     public function __destruct()
  366.     {
  367.         Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title);
  368.         $this->disconnectCells();
  369.         $this->rowDimensions = [];
  370.     }
  371.     /**
  372.      * Return the cell collection.
  373.      *
  374.      * @return Cells
  375.      */
  376.     public function getCellCollection()
  377.     {
  378.         return $this->cellCollection;
  379.     }
  380.     /**
  381.      * Get array of invalid characters for sheet title.
  382.      *
  383.      * @return array
  384.      */
  385.     public static function getInvalidCharacters()
  386.     {
  387.         return self::$invalidCharacters;
  388.     }
  389.     /**
  390.      * Check sheet code name for valid Excel syntax.
  391.      *
  392.      * @param string $sheetCodeName The string to check
  393.      *
  394.      * @return string The valid string
  395.      */
  396.     private static function checkSheetCodeName($sheetCodeName)
  397.     {
  398.         $charCount Shared\StringHelper::countCharacters($sheetCodeName);
  399.         if ($charCount == 0) {
  400.             throw new Exception('Sheet code name cannot be empty.');
  401.         }
  402.         // Some of the printable ASCII characters are invalid:  * : / \ ? [ ] and  first and last characters cannot be a "'"
  403.         if (
  404.             (str_replace(self::$invalidCharacters''$sheetCodeName) !== $sheetCodeName) ||
  405.             (Shared\StringHelper::substring($sheetCodeName, -11) == '\'') ||
  406.             (Shared\StringHelper::substring($sheetCodeName01) == '\'')
  407.         ) {
  408.             throw new Exception('Invalid character found in sheet code name');
  409.         }
  410.         // Enforce maximum characters allowed for sheet title
  411.         if ($charCount self::SHEET_TITLE_MAXIMUM_LENGTH) {
  412.             throw new Exception('Maximum ' self::SHEET_TITLE_MAXIMUM_LENGTH ' characters allowed in sheet code name.');
  413.         }
  414.         return $sheetCodeName;
  415.     }
  416.     /**
  417.      * Check sheet title for valid Excel syntax.
  418.      *
  419.      * @param string $sheetTitle The string to check
  420.      *
  421.      * @return string The valid string
  422.      */
  423.     private static function checkSheetTitle($sheetTitle)
  424.     {
  425.         // Some of the printable ASCII characters are invalid:  * : / \ ? [ ]
  426.         if (str_replace(self::$invalidCharacters''$sheetTitle) !== $sheetTitle) {
  427.             throw new Exception('Invalid character found in sheet title');
  428.         }
  429.         // Enforce maximum characters allowed for sheet title
  430.         if (Shared\StringHelper::countCharacters($sheetTitle) > self::SHEET_TITLE_MAXIMUM_LENGTH) {
  431.             throw new Exception('Maximum ' self::SHEET_TITLE_MAXIMUM_LENGTH ' characters allowed in sheet title.');
  432.         }
  433.         return $sheetTitle;
  434.     }
  435.     /**
  436.      * Get a sorted list of all cell coordinates currently held in the collection by row and column.
  437.      *
  438.      * @param bool $sorted Also sort the cell collection?
  439.      *
  440.      * @return string[]
  441.      */
  442.     public function getCoordinates($sorted true)
  443.     {
  444.         if ($this->cellCollection == null) {
  445.             return [];
  446.         }
  447.         if ($sorted) {
  448.             return $this->cellCollection->getSortedCoordinates();
  449.         }
  450.         return $this->cellCollection->getCoordinates();
  451.     }
  452.     /**
  453.      * Get collection of row dimensions.
  454.      *
  455.      * @return RowDimension[]
  456.      */
  457.     public function getRowDimensions()
  458.     {
  459.         return $this->rowDimensions;
  460.     }
  461.     /**
  462.      * Get default row dimension.
  463.      *
  464.      * @return RowDimension
  465.      */
  466.     public function getDefaultRowDimension()
  467.     {
  468.         return $this->defaultRowDimension;
  469.     }
  470.     /**
  471.      * Get collection of column dimensions.
  472.      *
  473.      * @return ColumnDimension[]
  474.      */
  475.     public function getColumnDimensions()
  476.     {
  477.         /** @var callable */
  478.         $callable = [self::class, 'columnDimensionCompare'];
  479.         uasort($this->columnDimensions$callable);
  480.         return $this->columnDimensions;
  481.     }
  482.     private static function columnDimensionCompare(ColumnDimension $aColumnDimension $b): int
  483.     {
  484.         return $a->getColumnNumeric() - $b->getColumnNumeric();
  485.     }
  486.     /**
  487.      * Get default column dimension.
  488.      *
  489.      * @return ColumnDimension
  490.      */
  491.     public function getDefaultColumnDimension()
  492.     {
  493.         return $this->defaultColumnDimension;
  494.     }
  495.     /**
  496.      * Get collection of drawings.
  497.      *
  498.      * @return ArrayObject<int, BaseDrawing>
  499.      */
  500.     public function getDrawingCollection()
  501.     {
  502.         return $this->drawingCollection;
  503.     }
  504.     /**
  505.      * Get collection of charts.
  506.      *
  507.      * @return ArrayObject<int, Chart>
  508.      */
  509.     public function getChartCollection()
  510.     {
  511.         return $this->chartCollection;
  512.     }
  513.     /**
  514.      * Add chart.
  515.      *
  516.      * @param null|int $chartIndex Index where chart should go (0,1,..., or null for last)
  517.      *
  518.      * @return Chart
  519.      */
  520.     public function addChart(Chart $chart$chartIndex null)
  521.     {
  522.         $chart->setWorksheet($this);
  523.         if ($chartIndex === null) {
  524.             $this->chartCollection[] = $chart;
  525.         } else {
  526.             // Insert the chart at the requested index
  527.             // @phpstan-ignore-next-line
  528.             array_splice(/** @scrutinizer ignore-type */ $this->chartCollection$chartIndex0, [$chart]);
  529.         }
  530.         return $chart;
  531.     }
  532.     /**
  533.      * Return the count of charts on this worksheet.
  534.      *
  535.      * @return int The number of charts
  536.      */
  537.     public function getChartCount()
  538.     {
  539.         return count($this->chartCollection);
  540.     }
  541.     /**
  542.      * Get a chart by its index position.
  543.      *
  544.      * @param ?string $index Chart index position
  545.      *
  546.      * @return Chart|false
  547.      */
  548.     public function getChartByIndex($index)
  549.     {
  550.         $chartCount count($this->chartCollection);
  551.         if ($chartCount == 0) {
  552.             return false;
  553.         }
  554.         if ($index === null) {
  555.             $index = --$chartCount;
  556.         }
  557.         if (!isset($this->chartCollection[$index])) {
  558.             return false;
  559.         }
  560.         return $this->chartCollection[$index];
  561.     }
  562.     /**
  563.      * Return an array of the names of charts on this worksheet.
  564.      *
  565.      * @return string[] The names of charts
  566.      */
  567.     public function getChartNames()
  568.     {
  569.         $chartNames = [];
  570.         foreach ($this->chartCollection as $chart) {
  571.             $chartNames[] = $chart->getName();
  572.         }
  573.         return $chartNames;
  574.     }
  575.     /**
  576.      * Get a chart by name.
  577.      *
  578.      * @param string $chartName Chart name
  579.      *
  580.      * @return Chart|false
  581.      */
  582.     public function getChartByName($chartName)
  583.     {
  584.         foreach ($this->chartCollection as $index => $chart) {
  585.             if ($chart->getName() == $chartName) {
  586.                 return $chart;
  587.             }
  588.         }
  589.         return false;
  590.     }
  591.     /**
  592.      * Refresh column dimensions.
  593.      *
  594.      * @return $this
  595.      */
  596.     public function refreshColumnDimensions()
  597.     {
  598.         $newColumnDimensions = [];
  599.         foreach ($this->getColumnDimensions() as $objColumnDimension) {
  600.             $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension;
  601.         }
  602.         $this->columnDimensions $newColumnDimensions;
  603.         return $this;
  604.     }
  605.     /**
  606.      * Refresh row dimensions.
  607.      *
  608.      * @return $this
  609.      */
  610.     public function refreshRowDimensions()
  611.     {
  612.         $newRowDimensions = [];
  613.         foreach ($this->getRowDimensions() as $objRowDimension) {
  614.             $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension;
  615.         }
  616.         $this->rowDimensions $newRowDimensions;
  617.         return $this;
  618.     }
  619.     /**
  620.      * Calculate worksheet dimension.
  621.      *
  622.      * @return string String containing the dimension of this worksheet
  623.      */
  624.     public function calculateWorksheetDimension()
  625.     {
  626.         // Return
  627.         return 'A1:' $this->getHighestColumn() . $this->getHighestRow();
  628.     }
  629.     /**
  630.      * Calculate worksheet data dimension.
  631.      *
  632.      * @return string String containing the dimension of this worksheet that actually contain data
  633.      */
  634.     public function calculateWorksheetDataDimension()
  635.     {
  636.         // Return
  637.         return 'A1:' $this->getHighestDataColumn() . $this->getHighestDataRow();
  638.     }
  639.     /**
  640.      * Calculate widths for auto-size columns.
  641.      *
  642.      * @return $this
  643.      */
  644.     public function calculateColumnWidths()
  645.     {
  646.         // initialize $autoSizes array
  647.         $autoSizes = [];
  648.         foreach ($this->getColumnDimensions() as $colDimension) {
  649.             if ($colDimension->getAutoSize()) {
  650.                 $autoSizes[$colDimension->getColumnIndex()] = -1;
  651.             }
  652.         }
  653.         // There is only something to do if there are some auto-size columns
  654.         if (!empty($autoSizes)) {
  655.             // build list of cells references that participate in a merge
  656.             $isMergeCell = [];
  657.             foreach ($this->getMergeCells() as $cells) {
  658.                 foreach (Coordinate::extractAllCellReferencesInRange($cells) as $cellReference) {
  659.                     $isMergeCell[$cellReference] = true;
  660.                 }
  661.             }
  662.             $autoFilterIndentRanges = (new AutoFit($this))->getAutoFilterIndentRanges();
  663.             // loop through all cells in the worksheet
  664.             foreach ($this->getCoordinates(false) as $coordinate) {
  665.                 $cell $this->getCellOrNull($coordinate);
  666.                 if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
  667.                     //Determine if cell is in merge range
  668.                     $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]);
  669.                     //By default merged cells should be ignored
  670.                     $isMergedButProceed false;
  671.                     //The only exception is if it's a merge range value cell of a 'vertical' range (1 column wide)
  672.                     if ($isMerged && $cell->isMergeRangeValueCell()) {
  673.                         $range = (string) $cell->getMergeRange();
  674.                         $rangeBoundaries Coordinate::rangeDimension($range);
  675.                         if ($rangeBoundaries[0] === 1) {
  676.                             $isMergedButProceed true;
  677.                         }
  678.                     }
  679.                     // Determine width if cell is not part of a merge or does and is a value cell of 1-column wide range
  680.                     if (!$isMerged || $isMergedButProceed) {
  681.                         // Determine if we need to make an adjustment for the first row in an AutoFilter range that
  682.                         //    has a column filter dropdown
  683.                         $filterAdjustment false;
  684.                         if (!empty($autoFilterIndentRanges)) {
  685.                             foreach ($autoFilterIndentRanges as $autoFilterFirstRowRange) {
  686.                                 if ($cell->isInRange($autoFilterFirstRowRange)) {
  687.                                     $filterAdjustment true;
  688.                                     break;
  689.                                 }
  690.                             }
  691.                         }
  692.                         $indentAdjustment $cell->getStyle()->getAlignment()->getIndent();
  693.                         $indentAdjustment += (int) ($cell->getStyle()->getAlignment()->getHorizontal() === Alignment::HORIZONTAL_CENTER);
  694.                         // Calculated value
  695.                         // To formatted string
  696.                         $cellValue NumberFormat::toFormattedString(
  697.                             $cell->getCalculatedValue(),
  698.                             (string) $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())
  699.                                 ->getNumberFormat()->getFormatCode()
  700.                         );
  701.                         if ($cellValue !== null && $cellValue !== '') {
  702.                             $autoSizes[$this->cellCollection->getCurrentColumn()] = max(
  703.                                 $autoSizes[$this->cellCollection->getCurrentColumn()],
  704.                                 round(
  705.                                     Shared\Font::calculateColumnWidth(
  706.                                         $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())->getFont(),
  707.                                         $cellValue,
  708.                                         (int) $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())
  709.                                             ->getAlignment()->getTextRotation(),
  710.                                         $this->getParentOrThrow()->getDefaultStyle()->getFont(),
  711.                                         $filterAdjustment,
  712.                                         $indentAdjustment
  713.                                     ),
  714.                                     3
  715.                                 )
  716.                             );
  717.                         }
  718.                     }
  719.                 }
  720.             }
  721.             // adjust column widths
  722.             foreach ($autoSizes as $columnIndex => $width) {
  723.                 if ($width == -1) {
  724.                     $width $this->getDefaultColumnDimension()->getWidth();
  725.                 }
  726.                 $this->getColumnDimension($columnIndex)->setWidth($width);
  727.             }
  728.         }
  729.         return $this;
  730.     }
  731.     /**
  732.      * Get parent or null.
  733.      */
  734.     public function getParent(): ?Spreadsheet
  735.     {
  736.         return $this->parent;
  737.     }
  738.     /**
  739.      * Get parent, throw exception if null.
  740.      */
  741.     public function getParentOrThrow(): Spreadsheet
  742.     {
  743.         if ($this->parent !== null) {
  744.             return $this->parent;
  745.         }
  746.         throw new Exception('Sheet does not have a parent.');
  747.     }
  748.     /**
  749.      * Re-bind parent.
  750.      *
  751.      * @return $this
  752.      */
  753.     public function rebindParent(Spreadsheet $parent)
  754.     {
  755.         if ($this->parent !== null) {
  756.             $definedNames $this->parent->getDefinedNames();
  757.             foreach ($definedNames as $definedName) {
  758.                 $parent->addDefinedName($definedName);
  759.             }
  760.             $this->parent->removeSheetByIndex(
  761.                 $this->parent->getIndex($this)
  762.             );
  763.         }
  764.         $this->parent $parent;
  765.         return $this;
  766.     }
  767.     /**
  768.      * Get title.
  769.      *
  770.      * @return string
  771.      */
  772.     public function getTitle()
  773.     {
  774.         return $this->title;
  775.     }
  776.     /**
  777.      * Set title.
  778.      *
  779.      * @param string $title String containing the dimension of this worksheet
  780.      * @param bool $updateFormulaCellReferences Flag indicating whether cell references in formulae should
  781.      *            be updated to reflect the new sheet name.
  782.      *          This should be left as the default true, unless you are
  783.      *          certain that no formula cells on any worksheet contain
  784.      *          references to this worksheet
  785.      * @param bool $validate False to skip validation of new title. WARNING: This should only be set
  786.      *                       at parse time (by Readers), where titles can be assumed to be valid.
  787.      *
  788.      * @return $this
  789.      */
  790.     public function setTitle($title$updateFormulaCellReferences true$validate true)
  791.     {
  792.         // Is this a 'rename' or not?
  793.         if ($this->getTitle() == $title) {
  794.             return $this;
  795.         }
  796.         // Old title
  797.         $oldTitle $this->getTitle();
  798.         if ($validate) {
  799.             // Syntax check
  800.             self::checkSheetTitle($title);
  801.             if ($this->parent) {
  802.                 // Is there already such sheet name?
  803.                 if ($this->parent->sheetNameExists($title)) {
  804.                     // Use name, but append with lowest possible integer
  805.                     if (Shared\StringHelper::countCharacters($title) > 29) {
  806.                         $title Shared\StringHelper::substring($title029);
  807.                     }
  808.                     $i 1;
  809.                     while ($this->parent->sheetNameExists($title ' ' $i)) {
  810.                         ++$i;
  811.                         if ($i == 10) {
  812.                             if (Shared\StringHelper::countCharacters($title) > 28) {
  813.                                 $title Shared\StringHelper::substring($title028);
  814.                             }
  815.                         } elseif ($i == 100) {
  816.                             if (Shared\StringHelper::countCharacters($title) > 27) {
  817.                                 $title Shared\StringHelper::substring($title027);
  818.                             }
  819.                         }
  820.                     }
  821.                     $title .= $i";
  822.                 }
  823.             }
  824.         }
  825.         // Set title
  826.         $this->title $title;
  827.         $this->dirty true;
  828.         if ($this->parent && $this->parent->getCalculationEngine()) {
  829.             // New title
  830.             $newTitle $this->getTitle();
  831.             $this->parent->getCalculationEngine()
  832.                 ->renameCalculationCacheForWorksheet($oldTitle$newTitle);
  833.             if ($updateFormulaCellReferences) {
  834.                 ReferenceHelper::getInstance()->updateNamedFormulae($this->parent$oldTitle$newTitle);
  835.             }
  836.         }
  837.         return $this;
  838.     }
  839.     /**
  840.      * Get sheet state.
  841.      *
  842.      * @return string Sheet state (visible, hidden, veryHidden)
  843.      */
  844.     public function getSheetState()
  845.     {
  846.         return $this->sheetState;
  847.     }
  848.     /**
  849.      * Set sheet state.
  850.      *
  851.      * @param string $value Sheet state (visible, hidden, veryHidden)
  852.      *
  853.      * @return $this
  854.      */
  855.     public function setSheetState($value)
  856.     {
  857.         $this->sheetState $value;
  858.         return $this;
  859.     }
  860.     /**
  861.      * Get page setup.
  862.      *
  863.      * @return PageSetup
  864.      */
  865.     public function getPageSetup()
  866.     {
  867.         return $this->pageSetup;
  868.     }
  869.     /**
  870.      * Set page setup.
  871.      *
  872.      * @return $this
  873.      */
  874.     public function setPageSetup(PageSetup $pageSetup)
  875.     {
  876.         $this->pageSetup $pageSetup;
  877.         return $this;
  878.     }
  879.     /**
  880.      * Get page margins.
  881.      *
  882.      * @return PageMargins
  883.      */
  884.     public function getPageMargins()
  885.     {
  886.         return $this->pageMargins;
  887.     }
  888.     /**
  889.      * Set page margins.
  890.      *
  891.      * @return $this
  892.      */
  893.     public function setPageMargins(PageMargins $pageMargins)
  894.     {
  895.         $this->pageMargins $pageMargins;
  896.         return $this;
  897.     }
  898.     /**
  899.      * Get page header/footer.
  900.      *
  901.      * @return HeaderFooter
  902.      */
  903.     public function getHeaderFooter()
  904.     {
  905.         return $this->headerFooter;
  906.     }
  907.     /**
  908.      * Set page header/footer.
  909.      *
  910.      * @return $this
  911.      */
  912.     public function setHeaderFooter(HeaderFooter $headerFooter)
  913.     {
  914.         $this->headerFooter $headerFooter;
  915.         return $this;
  916.     }
  917.     /**
  918.      * Get sheet view.
  919.      *
  920.      * @return SheetView
  921.      */
  922.     public function getSheetView()
  923.     {
  924.         return $this->sheetView;
  925.     }
  926.     /**
  927.      * Set sheet view.
  928.      *
  929.      * @return $this
  930.      */
  931.     public function setSheetView(SheetView $sheetView)
  932.     {
  933.         $this->sheetView $sheetView;
  934.         return $this;
  935.     }
  936.     /**
  937.      * Get Protection.
  938.      *
  939.      * @return Protection
  940.      */
  941.     public function getProtection()
  942.     {
  943.         return $this->protection;
  944.     }
  945.     /**
  946.      * Set Protection.
  947.      *
  948.      * @return $this
  949.      */
  950.     public function setProtection(Protection $protection)
  951.     {
  952.         $this->protection $protection;
  953.         $this->dirty true;
  954.         return $this;
  955.     }
  956.     /**
  957.      * Get highest worksheet column.
  958.      *
  959.      * @param null|int|string $row Return the data highest column for the specified row,
  960.      *                                     or the highest column of any row if no row number is passed
  961.      *
  962.      * @return string Highest column name
  963.      */
  964.     public function getHighestColumn($row null)
  965.     {
  966.         if ($row === null) {
  967.             return Coordinate::stringFromColumnIndex($this->cachedHighestColumn);
  968.         }
  969.         return $this->getHighestDataColumn($row);
  970.     }
  971.     /**
  972.      * Get highest worksheet column that contains data.
  973.      *
  974.      * @param null|int|string $row Return the highest data column for the specified row,
  975.      *                                     or the highest data column of any row if no row number is passed
  976.      *
  977.      * @return string Highest column name that contains data
  978.      */
  979.     public function getHighestDataColumn($row null)
  980.     {
  981.         return $this->cellCollection->getHighestColumn($row);
  982.     }
  983.     /**
  984.      * Get highest worksheet row.
  985.      *
  986.      * @param null|string $column Return the highest data row for the specified column,
  987.      *                                     or the highest row of any column if no column letter is passed
  988.      *
  989.      * @return int Highest row number
  990.      */
  991.     public function getHighestRow($column null)
  992.     {
  993.         if ($column === null) {
  994.             return $this->cachedHighestRow;
  995.         }
  996.         return $this->getHighestDataRow($column);
  997.     }
  998.     /**
  999.      * Get highest worksheet row that contains data.
  1000.      *
  1001.      * @param null|string $column Return the highest data row for the specified column,
  1002.      *                                     or the highest data row of any column if no column letter is passed
  1003.      *
  1004.      * @return int Highest row number that contains data
  1005.      */
  1006.     public function getHighestDataRow($column null)
  1007.     {
  1008.         return $this->cellCollection->getHighestRow($column);
  1009.     }
  1010.     /**
  1011.      * Get highest worksheet column and highest row that have cell records.
  1012.      *
  1013.      * @return array Highest column name and highest row number
  1014.      */
  1015.     public function getHighestRowAndColumn()
  1016.     {
  1017.         return $this->cellCollection->getHighestRowAndColumn();
  1018.     }
  1019.     /**
  1020.      * Set a cell value.
  1021.      *
  1022.      * @param array<int>|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5';
  1023.      *               or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  1024.      * @param mixed $value Value for the cell
  1025.      * @param null|IValueBinder $binder Value Binder to override the currently set Value Binder
  1026.      *
  1027.      * @return $this
  1028.      */
  1029.     public function setCellValue($coordinate$value, ?IValueBinder $binder null)
  1030.     {
  1031.         $cellAddress Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
  1032.         $this->getCell($cellAddress)->setValue($value$binder);
  1033.         return $this;
  1034.     }
  1035.     /**
  1036.      * Set a cell value by using numeric cell coordinates.
  1037.      *
  1038.      * @deprecated 1.23.0
  1039.      *      Use the setCellValue() method with a cell address such as 'C5' instead;,
  1040.      *          or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  1041.      * @see Worksheet::setCellValue()
  1042.      *
  1043.      * @param int $columnIndex Numeric column coordinate of the cell
  1044.      * @param int $row Numeric row coordinate of the cell
  1045.      * @param mixed $value Value of the cell
  1046.      * @param null|IValueBinder $binder Value Binder to override the currently set Value Binder
  1047.      *
  1048.      * @return $this
  1049.      */
  1050.     public function setCellValueByColumnAndRow($columnIndex$row$value, ?IValueBinder $binder null)
  1051.     {
  1052.         $this->getCell(Coordinate::stringFromColumnIndex($columnIndex) . $row)->setValue($value$binder);
  1053.         return $this;
  1054.     }
  1055.     /**
  1056.      * Set a cell value.
  1057.      *
  1058.      * @param array<int>|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5';
  1059.      *               or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  1060.      * @param mixed $value Value of the cell
  1061.      * @param string $dataType Explicit data type, see DataType::TYPE_*
  1062.      *        Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this
  1063.      *             method, then it is your responsibility as an end-user developer to validate that the value and
  1064.      *             the datatype match.
  1065.      *       If you do mismatch value and datatpe, then the value you enter may be changed to match the datatype
  1066.      *          that you specify.
  1067.      *
  1068.      * @see DataType
  1069.      *
  1070.      * @return $this
  1071.      */
  1072.     public function setCellValueExplicit($coordinate$value$dataType)
  1073.     {
  1074.         $cellAddress Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
  1075.         $this->getCell($cellAddress)->setValueExplicit($value$dataType);
  1076.         return $this;
  1077.     }
  1078.     /**
  1079.      * Set a cell value by using numeric cell coordinates.
  1080.      *
  1081.      * @deprecated 1.23.0
  1082.      *      Use the setCellValueExplicit() method with a cell address such as 'C5' instead;,
  1083.      *          or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  1084.      * @see Worksheet::setCellValueExplicit()
  1085.      *
  1086.      * @param int $columnIndex Numeric column coordinate of the cell
  1087.      * @param int $row Numeric row coordinate of the cell
  1088.      * @param mixed $value Value of the cell
  1089.      * @param string $dataType Explicit data type, see DataType::TYPE_*
  1090.      *        Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this
  1091.      *             method, then it is your responsibility as an end-user developer to validate that the value and
  1092.      *             the datatype match.
  1093.      *       If you do mismatch value and datatpe, then the value you enter may be changed to match the datatype
  1094.      *          that you specify.
  1095.      *
  1096.      * @see DataType
  1097.      *
  1098.      * @return $this
  1099.      */
  1100.     public function setCellValueExplicitByColumnAndRow($columnIndex$row$value$dataType)
  1101.     {
  1102.         $this->getCell(Coordinate::stringFromColumnIndex($columnIndex) . $row)->setValueExplicit($value$dataType);
  1103.         return $this;
  1104.     }
  1105.     /**
  1106.      * Get cell at a specific coordinate.
  1107.      *
  1108.      * @param array<int>|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5';
  1109.      *               or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  1110.      *
  1111.      * @return Cell Cell that was found or created
  1112.      *              WARNING: Because the cell collection can be cached to reduce memory, it only allows one
  1113.      *              "active" cell at a time in memory. If you assign that cell to a variable, then select
  1114.      *              another cell using getCell() or any of its variants, the newly selected cell becomes
  1115.      *              the "active" cell, and any previous assignment becomes a disconnected reference because
  1116.      *              the active cell has changed.
  1117.      */
  1118.     public function getCell($coordinate): Cell
  1119.     {
  1120.         $cellAddress Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
  1121.         // Shortcut for increased performance for the vast majority of simple cases
  1122.         if ($this->cellCollection->has($cellAddress)) {
  1123.             /** @var Cell $cell */
  1124.             $cell $this->cellCollection->get($cellAddress);
  1125.             return $cell;
  1126.         }
  1127.         /** @var Worksheet $sheet */
  1128.         [$sheet$finalCoordinate] = $this->getWorksheetAndCoordinate($cellAddress);
  1129.         $cell $sheet->cellCollection->get($finalCoordinate);
  1130.         return $cell ?? $sheet->createNewCell($finalCoordinate);
  1131.     }
  1132.     /**
  1133.      * Get the correct Worksheet and coordinate from a coordinate that may
  1134.      * contains reference to another sheet or a named range.
  1135.      *
  1136.      * @return array{0: Worksheet, 1: string}
  1137.      */
  1138.     private function getWorksheetAndCoordinate(string $coordinate): array
  1139.     {
  1140.         $sheet null;
  1141.         $finalCoordinate null;
  1142.         // Worksheet reference?
  1143.         if (strpos($coordinate'!') !== false) {
  1144.             $worksheetReference self::extractSheetTitle($coordinatetrue);
  1145.             $sheet $this->getParentOrThrow()->getSheetByName($worksheetReference[0]);
  1146.             $finalCoordinate strtoupper($worksheetReference[1]);
  1147.             if ($sheet === null) {
  1148.                 throw new Exception('Sheet not found for name: ' $worksheetReference[0]);
  1149.             }
  1150.         } elseif (
  1151.             !preg_match('/^' Calculation::CALCULATION_REGEXP_CELLREF '$/i'$coordinate) &&
  1152.             preg_match('/^' Calculation::CALCULATION_REGEXP_DEFINEDNAME '$/iu'$coordinate)
  1153.         ) {
  1154.             // Named range?
  1155.             $namedRange $this->validateNamedRange($coordinatetrue);
  1156.             if ($namedRange !== null) {
  1157.                 $sheet $namedRange->getWorksheet();
  1158.                 if ($sheet === null) {
  1159.                     throw new Exception('Sheet not found for named range: ' $namedRange->getName());
  1160.                 }
  1161.                 /** @phpstan-ignore-next-line */
  1162.                 $cellCoordinate ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!');
  1163.                 $finalCoordinate str_replace('$'''$cellCoordinate);
  1164.             }
  1165.         }
  1166.         if ($sheet === null || $finalCoordinate === null) {
  1167.             $sheet $this;
  1168.             $finalCoordinate strtoupper($coordinate);
  1169.         }
  1170.         if (Coordinate::coordinateIsRange($finalCoordinate)) {
  1171.             throw new Exception('Cell coordinate string can not be a range of cells.');
  1172.         } elseif (strpos($finalCoordinate'$') !== false) {
  1173.             throw new Exception('Cell coordinate must not be absolute.');
  1174.         }
  1175.         return [$sheet$finalCoordinate];
  1176.     }
  1177.     /**
  1178.      * Get an existing cell at a specific coordinate, or null.
  1179.      *
  1180.      * @param string $coordinate Coordinate of the cell, eg: 'A1'
  1181.      *
  1182.      * @return null|Cell Cell that was found or null
  1183.      */
  1184.     private function getCellOrNull($coordinate): ?Cell
  1185.     {
  1186.         // Check cell collection
  1187.         if ($this->cellCollection->has($coordinate)) {
  1188.             return $this->cellCollection->get($coordinate);
  1189.         }
  1190.         return null;
  1191.     }
  1192.     /**
  1193.      * Get cell at a specific coordinate by using numeric cell coordinates.
  1194.      *
  1195.      * @deprecated 1.23.0
  1196.      *      Use the getCell() method with a cell address such as 'C5' instead;,
  1197.      *          or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  1198.      * @see Worksheet::getCell()
  1199.      *
  1200.      * @param int $columnIndex Numeric column coordinate of the cell
  1201.      * @param int $row Numeric row coordinate of the cell
  1202.      *
  1203.      * @return Cell Cell that was found/created or null
  1204.      *              WARNING: Because the cell collection can be cached to reduce memory, it only allows one
  1205.      *              "active" cell at a time in memory. If you assign that cell to a variable, then select
  1206.      *              another cell using getCell() or any of its variants, the newly selected cell becomes
  1207.      *              the "active" cell, and any previous assignment becomes a disconnected reference because
  1208.      *              the active cell has changed.
  1209.      */
  1210.     public function getCellByColumnAndRow($columnIndex$row): Cell
  1211.     {
  1212.         return $this->getCell(Coordinate::stringFromColumnIndex($columnIndex) . $row);
  1213.     }
  1214.     /**
  1215.      * Create a new cell at the specified coordinate.
  1216.      *
  1217.      * @param string $coordinate Coordinate of the cell
  1218.      *
  1219.      * @return Cell Cell that was created
  1220.      *              WARNING: Because the cell collection can be cached to reduce memory, it only allows one
  1221.      *              "active" cell at a time in memory. If you assign that cell to a variable, then select
  1222.      *              another cell using getCell() or any of its variants, the newly selected cell becomes
  1223.      *              the "active" cell, and any previous assignment becomes a disconnected reference because
  1224.      *              the active cell has changed.
  1225.      */
  1226.     public function createNewCell($coordinate): Cell
  1227.     {
  1228.         [$column$row$columnString] = Coordinate::indexesFromString($coordinate);
  1229.         $cell = new Cell(nullDataType::TYPE_NULL$this);
  1230.         $this->cellCollection->add($coordinate$cell);
  1231.         // Coordinates
  1232.         if ($column $this->cachedHighestColumn) {
  1233.             $this->cachedHighestColumn $column;
  1234.         }
  1235.         if ($row $this->cachedHighestRow) {
  1236.             $this->cachedHighestRow $row;
  1237.         }
  1238.         // Cell needs appropriate xfIndex from dimensions records
  1239.         //    but don't create dimension records if they don't already exist
  1240.         $rowDimension $this->rowDimensions[$row] ?? null;
  1241.         $columnDimension $this->columnDimensions[$columnString] ?? null;
  1242.         if ($rowDimension !== null) {
  1243.             $rowXf = (int) $rowDimension->getXfIndex();
  1244.             if ($rowXf 0) {
  1245.                 // then there is a row dimension with explicit style, assign it to the cell
  1246.                 $cell->setXfIndex($rowXf);
  1247.             }
  1248.         } elseif ($columnDimension !== null) {
  1249.             $colXf = (int) $columnDimension->getXfIndex();
  1250.             if ($colXf 0) {
  1251.                 // then there is a column dimension, assign it to the cell
  1252.                 $cell->setXfIndex($colXf);
  1253.             }
  1254.         }
  1255.         return $cell;
  1256.     }
  1257.     /**
  1258.      * Does the cell at a specific coordinate exist?
  1259.      *
  1260.      * @param array<int>|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5';
  1261.      *               or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  1262.      */
  1263.     public function cellExists($coordinate): bool
  1264.     {
  1265.         $cellAddress Validations::validateCellAddress($coordinate);
  1266.         /** @var Worksheet $sheet */
  1267.         [$sheet$finalCoordinate] = $this->getWorksheetAndCoordinate($cellAddress);
  1268.         return $sheet->cellCollection->has($finalCoordinate);
  1269.     }
  1270.     /**
  1271.      * Cell at a specific coordinate by using numeric cell coordinates exists?
  1272.      *
  1273.      * @deprecated 1.23.0
  1274.      *      Use the cellExists() method with a cell address such as 'C5' instead;,
  1275.      *          or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  1276.      * @see Worksheet::cellExists()
  1277.      *
  1278.      * @param int $columnIndex Numeric column coordinate of the cell
  1279.      * @param int $row Numeric row coordinate of the cell
  1280.      */
  1281.     public function cellExistsByColumnAndRow($columnIndex$row): bool
  1282.     {
  1283.         return $this->cellExists(Coordinate::stringFromColumnIndex($columnIndex) . $row);
  1284.     }
  1285.     /**
  1286.      * Get row dimension at a specific row.
  1287.      *
  1288.      * @param int $row Numeric index of the row
  1289.      */
  1290.     public function getRowDimension(int $row): RowDimension
  1291.     {
  1292.         // Get row dimension
  1293.         if (!isset($this->rowDimensions[$row])) {
  1294.             $this->rowDimensions[$row] = new RowDimension($row);
  1295.             $this->cachedHighestRow max($this->cachedHighestRow$row);
  1296.         }
  1297.         return $this->rowDimensions[$row];
  1298.     }
  1299.     public function rowDimensionExists(int $row): bool
  1300.     {
  1301.         return isset($this->rowDimensions[$row]);
  1302.     }
  1303.     /**
  1304.      * Get column dimension at a specific column.
  1305.      *
  1306.      * @param string $column String index of the column eg: 'A'
  1307.      */
  1308.     public function getColumnDimension(string $column): ColumnDimension
  1309.     {
  1310.         // Uppercase coordinate
  1311.         $column strtoupper($column);
  1312.         // Fetch dimensions
  1313.         if (!isset($this->columnDimensions[$column])) {
  1314.             $this->columnDimensions[$column] = new ColumnDimension($column);
  1315.             $columnIndex Coordinate::columnIndexFromString($column);
  1316.             if ($this->cachedHighestColumn $columnIndex) {
  1317.                 $this->cachedHighestColumn $columnIndex;
  1318.             }
  1319.         }
  1320.         return $this->columnDimensions[$column];
  1321.     }
  1322.     /**
  1323.      * Get column dimension at a specific column by using numeric cell coordinates.
  1324.      *
  1325.      * @param int $columnIndex Numeric column coordinate of the cell
  1326.      */
  1327.     public function getColumnDimensionByColumn(int $columnIndex): ColumnDimension
  1328.     {
  1329.         return $this->getColumnDimension(Coordinate::stringFromColumnIndex($columnIndex));
  1330.     }
  1331.     /**
  1332.      * Get styles.
  1333.      *
  1334.      * @return Style[]
  1335.      */
  1336.     public function getStyles()
  1337.     {
  1338.         return $this->styles;
  1339.     }
  1340.     /**
  1341.      * Get style for cell.
  1342.      *
  1343.      * @param AddressRange|array<int>|CellAddress|int|string $cellCoordinate
  1344.      *              A simple string containing a cell address like 'A1' or a cell range like 'A1:E10'
  1345.      *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1346.      *              or a CellAddress or AddressRange object.
  1347.      */
  1348.     public function getStyle($cellCoordinate): Style
  1349.     {
  1350.         $cellCoordinate Validations::validateCellOrCellRange($cellCoordinate);
  1351.         // set this sheet as active
  1352.         $this->getParentOrThrow()->setActiveSheetIndex($this->getParentOrThrow()->getIndex($this));
  1353.         // set cell coordinate as active
  1354.         $this->setSelectedCells($cellCoordinate);
  1355.         return $this->getParentOrThrow()->getCellXfSupervisor();
  1356.     }
  1357.     /**
  1358.      * Get style for cell by using numeric cell coordinates.
  1359.      *
  1360.      * @deprecated 1.23.0
  1361.      *      Use the getStyle() method with a cell address range such as 'C5:F8' instead;,
  1362.      *          or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1363.      *          or an AddressRange object.
  1364.      * @see Worksheet::getStyle()
  1365.      *
  1366.      * @param int $columnIndex1 Numeric column coordinate of the cell
  1367.      * @param int $row1 Numeric row coordinate of the cell
  1368.      * @param null|int $columnIndex2 Numeric column coordinate of the range cell
  1369.      * @param null|int $row2 Numeric row coordinate of the range cell
  1370.      *
  1371.      * @return Style
  1372.      */
  1373.     public function getStyleByColumnAndRow($columnIndex1$row1$columnIndex2 null$row2 null)
  1374.     {
  1375.         if ($columnIndex2 !== null && $row2 !== null) {
  1376.             $cellRange = new CellRange(
  1377.                 CellAddress::fromColumnAndRow($columnIndex1$row1),
  1378.                 CellAddress::fromColumnAndRow($columnIndex2$row2)
  1379.             );
  1380.             return $this->getStyle($cellRange);
  1381.         }
  1382.         return $this->getStyle(CellAddress::fromColumnAndRow($columnIndex1$row1));
  1383.     }
  1384.     /**
  1385.      * Get conditional styles for a cell.
  1386.      *
  1387.      * @param string $coordinate eg: 'A1' or 'A1:A3'.
  1388.      *          If a single cell is referenced, then the array of conditional styles will be returned if the cell is
  1389.      *               included in a conditional style range.
  1390.      *          If a range of cells is specified, then the styles will only be returned if the range matches the entire
  1391.      *               range of the conditional.
  1392.      *
  1393.      * @return Conditional[]
  1394.      */
  1395.     public function getConditionalStyles(string $coordinate): array
  1396.     {
  1397.         $coordinate strtoupper($coordinate);
  1398.         if (strpos($coordinate':') !== false) {
  1399.             return $this->conditionalStylesCollection[$coordinate] ?? [];
  1400.         }
  1401.         $cell $this->getCell($coordinate);
  1402.         foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) {
  1403.             if ($cell->isInRange($conditionalRange)) {
  1404.                 return $this->conditionalStylesCollection[$conditionalRange];
  1405.             }
  1406.         }
  1407.         return [];
  1408.     }
  1409.     public function getConditionalRange(string $coordinate): ?string
  1410.     {
  1411.         $coordinate strtoupper($coordinate);
  1412.         $cell $this->getCell($coordinate);
  1413.         foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) {
  1414.             if ($cell->isInRange($conditionalRange)) {
  1415.                 return $conditionalRange;
  1416.             }
  1417.         }
  1418.         return null;
  1419.     }
  1420.     /**
  1421.      * Do conditional styles exist for this cell?
  1422.      *
  1423.      * @param string $coordinate eg: 'A1' or 'A1:A3'.
  1424.      *          If a single cell is specified, then this method will return true if that cell is included in a
  1425.      *               conditional style range.
  1426.      *          If a range of cells is specified, then true will only be returned if the range matches the entire
  1427.      *               range of the conditional.
  1428.      */
  1429.     public function conditionalStylesExists($coordinate): bool
  1430.     {
  1431.         $coordinate strtoupper($coordinate);
  1432.         if (strpos($coordinate':') !== false) {
  1433.             return isset($this->conditionalStylesCollection[$coordinate]);
  1434.         }
  1435.         $cell $this->getCell($coordinate);
  1436.         foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) {
  1437.             if ($cell->isInRange($conditionalRange)) {
  1438.                 return true;
  1439.             }
  1440.         }
  1441.         return false;
  1442.     }
  1443.     /**
  1444.      * Removes conditional styles for a cell.
  1445.      *
  1446.      * @param string $coordinate eg: 'A1'
  1447.      *
  1448.      * @return $this
  1449.      */
  1450.     public function removeConditionalStyles($coordinate)
  1451.     {
  1452.         unset($this->conditionalStylesCollection[strtoupper($coordinate)]);
  1453.         return $this;
  1454.     }
  1455.     /**
  1456.      * Get collection of conditional styles.
  1457.      *
  1458.      * @return array
  1459.      */
  1460.     public function getConditionalStylesCollection()
  1461.     {
  1462.         return $this->conditionalStylesCollection;
  1463.     }
  1464.     /**
  1465.      * Set conditional styles.
  1466.      *
  1467.      * @param string $coordinate eg: 'A1'
  1468.      * @param Conditional[] $styles
  1469.      *
  1470.      * @return $this
  1471.      */
  1472.     public function setConditionalStyles($coordinate$styles)
  1473.     {
  1474.         $this->conditionalStylesCollection[strtoupper($coordinate)] = $styles;
  1475.         return $this;
  1476.     }
  1477.     /**
  1478.      * Duplicate cell style to a range of cells.
  1479.      *
  1480.      * Please note that this will overwrite existing cell styles for cells in range!
  1481.      *
  1482.      * @param Style $style Cell style to duplicate
  1483.      * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
  1484.      *
  1485.      * @return $this
  1486.      */
  1487.     public function duplicateStyle(Style $style$range)
  1488.     {
  1489.         // Add the style to the workbook if necessary
  1490.         $workbook $this->getParentOrThrow();
  1491.         if ($existingStyle $workbook->getCellXfByHashCode($style->getHashCode())) {
  1492.             // there is already such cell Xf in our collection
  1493.             $xfIndex $existingStyle->getIndex();
  1494.         } else {
  1495.             // we don't have such a cell Xf, need to add
  1496.             $workbook->addCellXf($style);
  1497.             $xfIndex $style->getIndex();
  1498.         }
  1499.         // Calculate range outer borders
  1500.         [$rangeStart$rangeEnd] = Coordinate::rangeBoundaries($range ':' $range);
  1501.         // Make sure we can loop upwards on rows and columns
  1502.         if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
  1503.             $tmp $rangeStart;
  1504.             $rangeStart $rangeEnd;
  1505.             $rangeEnd $tmp;
  1506.         }
  1507.         // Loop through cells and apply styles
  1508.         for ($col $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
  1509.             for ($row $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
  1510.                 $this->getCell(Coordinate::stringFromColumnIndex($col) . $row)->setXfIndex($xfIndex);
  1511.             }
  1512.         }
  1513.         return $this;
  1514.     }
  1515.     /**
  1516.      * Duplicate conditional style to a range of cells.
  1517.      *
  1518.      * Please note that this will overwrite existing cell styles for cells in range!
  1519.      *
  1520.      * @param Conditional[] $styles Cell style to duplicate
  1521.      * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
  1522.      *
  1523.      * @return $this
  1524.      */
  1525.     public function duplicateConditionalStyle(array $styles$range '')
  1526.     {
  1527.         foreach ($styles as $cellStyle) {
  1528.             if (!($cellStyle instanceof Conditional)) {
  1529.                 throw new Exception('Style is not a conditional style');
  1530.             }
  1531.         }
  1532.         // Calculate range outer borders
  1533.         [$rangeStart$rangeEnd] = Coordinate::rangeBoundaries($range ':' $range);
  1534.         // Make sure we can loop upwards on rows and columns
  1535.         if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
  1536.             $tmp $rangeStart;
  1537.             $rangeStart $rangeEnd;
  1538.             $rangeEnd $tmp;
  1539.         }
  1540.         // Loop through cells and apply styles
  1541.         for ($col $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
  1542.             for ($row $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
  1543.                 $this->setConditionalStyles(Coordinate::stringFromColumnIndex($col) . $row$styles);
  1544.             }
  1545.         }
  1546.         return $this;
  1547.     }
  1548.     /**
  1549.      * Set break on a cell.
  1550.      *
  1551.      * @param array<int>|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5';
  1552.      *               or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  1553.      * @param int $break Break type (type of Worksheet::BREAK_*)
  1554.      *
  1555.      * @return $this
  1556.      */
  1557.     public function setBreak($coordinate$breakint $max = -1)
  1558.     {
  1559.         $cellAddress Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
  1560.         if ($break === self::BREAK_NONE) {
  1561.             unset($this->rowBreaks[$cellAddress], $this->columnBreaks[$cellAddress]);
  1562.         } elseif ($break === self::BREAK_ROW) {
  1563.             $this->rowBreaks[$cellAddress] = new PageBreak($break$cellAddress$max);
  1564.         } elseif ($break === self::BREAK_COLUMN) {
  1565.             $this->columnBreaks[$cellAddress] = new PageBreak($break$cellAddress$max);
  1566.         }
  1567.         return $this;
  1568.     }
  1569.     /**
  1570.      * Set break on a cell by using numeric cell coordinates.
  1571.      *
  1572.      * @deprecated 1.23.0
  1573.      *      Use the setBreak() method with a cell address such as 'C5' instead;,
  1574.      *          or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  1575.      * @see Worksheet::setBreak()
  1576.      *
  1577.      * @param int $columnIndex Numeric column coordinate of the cell
  1578.      * @param int $row Numeric row coordinate of the cell
  1579.      * @param int $break Break type (type of Worksheet::BREAK_*)
  1580.      *
  1581.      * @return $this
  1582.      */
  1583.     public function setBreakByColumnAndRow($columnIndex$row$break)
  1584.     {
  1585.         return $this->setBreak(Coordinate::stringFromColumnIndex($columnIndex) . $row$break);
  1586.     }
  1587.     /**
  1588.      * Get breaks.
  1589.      *
  1590.      * @return int[]
  1591.      */
  1592.     public function getBreaks()
  1593.     {
  1594.         $breaks = [];
  1595.         /** @var callable */
  1596.         $compareFunction = [self::class, 'compareRowBreaks'];
  1597.         uksort($this->rowBreaks$compareFunction);
  1598.         foreach ($this->rowBreaks as $break) {
  1599.             $breaks[$break->getCoordinate()] = self::BREAK_ROW;
  1600.         }
  1601.         /** @var callable */
  1602.         $compareFunction = [self::class, 'compareColumnBreaks'];
  1603.         uksort($this->columnBreaks$compareFunction);
  1604.         foreach ($this->columnBreaks as $break) {
  1605.             $breaks[$break->getCoordinate()] = self::BREAK_COLUMN;
  1606.         }
  1607.         return $breaks;
  1608.     }
  1609.     /**
  1610.      * Get row breaks.
  1611.      *
  1612.      * @return PageBreak[]
  1613.      */
  1614.     public function getRowBreaks()
  1615.     {
  1616.         /** @var callable */
  1617.         $compareFunction = [self::class, 'compareRowBreaks'];
  1618.         uksort($this->rowBreaks$compareFunction);
  1619.         return $this->rowBreaks;
  1620.     }
  1621.     protected static function compareRowBreaks(string $coordinate1string $coordinate2): int
  1622.     {
  1623.         $row1 Coordinate::indexesFromString($coordinate1)[1];
  1624.         $row2 Coordinate::indexesFromString($coordinate2)[1];
  1625.         return $row1 $row2;
  1626.     }
  1627.     protected static function compareColumnBreaks(string $coordinate1string $coordinate2): int
  1628.     {
  1629.         $column1 Coordinate::indexesFromString($coordinate1)[0];
  1630.         $column2 Coordinate::indexesFromString($coordinate2)[0];
  1631.         return $column1 $column2;
  1632.     }
  1633.     /**
  1634.      * Get column breaks.
  1635.      *
  1636.      * @return PageBreak[]
  1637.      */
  1638.     public function getColumnBreaks()
  1639.     {
  1640.         /** @var callable */
  1641.         $compareFunction = [self::class, 'compareColumnBreaks'];
  1642.         uksort($this->columnBreaks$compareFunction);
  1643.         return $this->columnBreaks;
  1644.     }
  1645.     /**
  1646.      * Set merge on a cell range.
  1647.      *
  1648.      * @param AddressRange|array<int>|string $range A simple string containing a Cell range like 'A1:E10'
  1649.      *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1650.      *              or an AddressRange.
  1651.      * @param string $behaviour How the merged cells should behave.
  1652.      *               Possible values are:
  1653.      *                   MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells
  1654.      *                   MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells
  1655.      *                   MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell
  1656.      *
  1657.      * @return $this
  1658.      */
  1659.     public function mergeCells($range$behaviour self::MERGE_CELL_CONTENT_EMPTY)
  1660.     {
  1661.         $range Functions::trimSheetFromCellReference(Validations::validateCellRange($range));
  1662.         if (strpos($range':') === false) {
  1663.             $range .= ":{$range}";
  1664.         }
  1665.         if (preg_match('/^([A-Z]+)(\\d+):([A-Z]+)(\\d+)$/'$range$matches) !== 1) {
  1666.             throw new Exception('Merge must be on a valid range of cells.');
  1667.         }
  1668.         $this->mergeCells[$range] = $range;
  1669.         $firstRow = (int) $matches[2];
  1670.         $lastRow = (int) $matches[4];
  1671.         $firstColumn $matches[1];
  1672.         $lastColumn $matches[3];
  1673.         $firstColumnIndex Coordinate::columnIndexFromString($firstColumn);
  1674.         $lastColumnIndex Coordinate::columnIndexFromString($lastColumn);
  1675.         $numberRows $lastRow $firstRow;
  1676.         $numberColumns $lastColumnIndex $firstColumnIndex;
  1677.         if ($numberRows === && $numberColumns === 1) {
  1678.             return $this;
  1679.         }
  1680.         // create upper left cell if it does not already exist
  1681.         $upperLeft "{$firstColumn}{$firstRow}";
  1682.         if (!$this->cellExists($upperLeft)) {
  1683.             $this->getCell($upperLeft)->setValueExplicit(nullDataType::TYPE_NULL);
  1684.         }
  1685.         if ($behaviour !== self::MERGE_CELL_CONTENT_HIDE) {
  1686.             // Blank out the rest of the cells in the range (if they exist)
  1687.             if ($numberRows $numberColumns) {
  1688.                 $this->clearMergeCellsByColumn($firstColumn$lastColumn$firstRow$lastRow$upperLeft$behaviour);
  1689.             } else {
  1690.                 $this->clearMergeCellsByRow($firstColumn$lastColumnIndex$firstRow$lastRow$upperLeft$behaviour);
  1691.             }
  1692.         }
  1693.         return $this;
  1694.     }
  1695.     private function clearMergeCellsByColumn(string $firstColumnstring $lastColumnint $firstRowint $lastRowstring $upperLeftstring $behaviour): void
  1696.     {
  1697.         $leftCellValue = ($behaviour === self::MERGE_CELL_CONTENT_MERGE)
  1698.             ? [$this->getCell($upperLeft)->getFormattedValue()]
  1699.             : [];
  1700.         foreach ($this->getColumnIterator($firstColumn$lastColumn) as $column) {
  1701.             $iterator $column->getCellIterator($firstRow);
  1702.             $iterator->setIterateOnlyExistingCells(true);
  1703.             foreach ($iterator as $cell) {
  1704.                 if ($cell !== null) {
  1705.                     $row $cell->getRow();
  1706.                     if ($row $lastRow) {
  1707.                         break;
  1708.                     }
  1709.                     $leftCellValue $this->mergeCellBehaviour($cell$upperLeft$behaviour$leftCellValue);
  1710.                 }
  1711.             }
  1712.         }
  1713.         if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) {
  1714.             $this->getCell($upperLeft)->setValueExplicit(implode(' '$leftCellValue), DataType::TYPE_STRING);
  1715.         }
  1716.     }
  1717.     private function clearMergeCellsByRow(string $firstColumnint $lastColumnIndexint $firstRowint $lastRowstring $upperLeftstring $behaviour): void
  1718.     {
  1719.         $leftCellValue = ($behaviour === self::MERGE_CELL_CONTENT_MERGE)
  1720.             ? [$this->getCell($upperLeft)->getFormattedValue()]
  1721.             : [];
  1722.         foreach ($this->getRowIterator($firstRow$lastRow) as $row) {
  1723.             $iterator $row->getCellIterator($firstColumn);
  1724.             $iterator->setIterateOnlyExistingCells(true);
  1725.             foreach ($iterator as $cell) {
  1726.                 if ($cell !== null) {
  1727.                     $column $cell->getColumn();
  1728.                     $columnIndex Coordinate::columnIndexFromString($column);
  1729.                     if ($columnIndex $lastColumnIndex) {
  1730.                         break;
  1731.                     }
  1732.                     $leftCellValue $this->mergeCellBehaviour($cell$upperLeft$behaviour$leftCellValue);
  1733.                 }
  1734.             }
  1735.         }
  1736.         if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) {
  1737.             $this->getCell($upperLeft)->setValueExplicit(implode(' '$leftCellValue), DataType::TYPE_STRING);
  1738.         }
  1739.     }
  1740.     public function mergeCellBehaviour(Cell $cellstring $upperLeftstring $behaviour, array $leftCellValue): array
  1741.     {
  1742.         if ($cell->getCoordinate() !== $upperLeft) {
  1743.             Calculation::getInstance($cell->getWorksheet()->getParentOrThrow())->flushInstance();
  1744.             if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) {
  1745.                 $cellValue $cell->getFormattedValue();
  1746.                 if ($cellValue !== '') {
  1747.                     $leftCellValue[] = $cellValue;
  1748.                 }
  1749.             }
  1750.             $cell->setValueExplicit(nullDataType::TYPE_NULL);
  1751.         }
  1752.         return $leftCellValue;
  1753.     }
  1754.     /**
  1755.      * Set merge on a cell range by using numeric cell coordinates.
  1756.      *
  1757.      * @deprecated 1.23.0
  1758.      *      Use the mergeCells() method with a cell address range such as 'C5:F8' instead;,
  1759.      *          or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1760.      *          or an AddressRange object.
  1761.      * @see Worksheet::mergeCells()
  1762.      *
  1763.      * @param int $columnIndex1 Numeric column coordinate of the first cell
  1764.      * @param int $row1 Numeric row coordinate of the first cell
  1765.      * @param int $columnIndex2 Numeric column coordinate of the last cell
  1766.      * @param int $row2 Numeric row coordinate of the last cell
  1767.      * @param string $behaviour How the merged cells should behave.
  1768.      *               Possible values are:
  1769.      *                   MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells
  1770.      *                   MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells
  1771.      *                   MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell
  1772.      *
  1773.      * @return $this
  1774.      */
  1775.     public function mergeCellsByColumnAndRow($columnIndex1$row1$columnIndex2$row2$behaviour self::MERGE_CELL_CONTENT_EMPTY)
  1776.     {
  1777.         $cellRange = new CellRange(
  1778.             CellAddress::fromColumnAndRow($columnIndex1$row1),
  1779.             CellAddress::fromColumnAndRow($columnIndex2$row2)
  1780.         );
  1781.         return $this->mergeCells($cellRange$behaviour);
  1782.     }
  1783.     /**
  1784.      * Remove merge on a cell range.
  1785.      *
  1786.      * @param AddressRange|array<int>|string $range A simple string containing a Cell range like 'A1:E10'
  1787.      *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1788.      *              or an AddressRange.
  1789.      *
  1790.      * @return $this
  1791.      */
  1792.     public function unmergeCells($range)
  1793.     {
  1794.         $range Functions::trimSheetFromCellReference(Validations::validateCellRange($range));
  1795.         if (strpos($range':') !== false) {
  1796.             if (isset($this->mergeCells[$range])) {
  1797.                 unset($this->mergeCells[$range]);
  1798.             } else {
  1799.                 throw new Exception('Cell range ' $range ' not known as merged.');
  1800.             }
  1801.         } else {
  1802.             throw new Exception('Merge can only be removed from a range of cells.');
  1803.         }
  1804.         return $this;
  1805.     }
  1806.     /**
  1807.      * Remove merge on a cell range by using numeric cell coordinates.
  1808.      *
  1809.      * @deprecated 1.23.0
  1810.      *      Use the unmergeCells() method with a cell address range such as 'C5:F8' instead;,
  1811.      *          or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1812.      *          or an AddressRange object.
  1813.      * @see Worksheet::unmergeCells()
  1814.      *
  1815.      * @param int $columnIndex1 Numeric column coordinate of the first cell
  1816.      * @param int $row1 Numeric row coordinate of the first cell
  1817.      * @param int $columnIndex2 Numeric column coordinate of the last cell
  1818.      * @param int $row2 Numeric row coordinate of the last cell
  1819.      *
  1820.      * @return $this
  1821.      */
  1822.     public function unmergeCellsByColumnAndRow($columnIndex1$row1$columnIndex2$row2)
  1823.     {
  1824.         $cellRange = new CellRange(
  1825.             CellAddress::fromColumnAndRow($columnIndex1$row1),
  1826.             CellAddress::fromColumnAndRow($columnIndex2$row2)
  1827.         );
  1828.         return $this->unmergeCells($cellRange);
  1829.     }
  1830.     /**
  1831.      * Get merge cells array.
  1832.      *
  1833.      * @return string[]
  1834.      */
  1835.     public function getMergeCells()
  1836.     {
  1837.         return $this->mergeCells;
  1838.     }
  1839.     /**
  1840.      * Set merge cells array for the entire sheet. Use instead mergeCells() to merge
  1841.      * a single cell range.
  1842.      *
  1843.      * @param string[] $mergeCells
  1844.      *
  1845.      * @return $this
  1846.      */
  1847.     public function setMergeCells(array $mergeCells)
  1848.     {
  1849.         $this->mergeCells $mergeCells;
  1850.         return $this;
  1851.     }
  1852.     /**
  1853.      * Set protection on a cell or cell range.
  1854.      *
  1855.      * @param AddressRange|array<int>|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10'
  1856.      *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1857.      *              or a CellAddress or AddressRange object.
  1858.      * @param string $password Password to unlock the protection
  1859.      * @param bool $alreadyHashed If the password has already been hashed, set this to true
  1860.      *
  1861.      * @return $this
  1862.      */
  1863.     public function protectCells($range$password$alreadyHashed false)
  1864.     {
  1865.         $range Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range));
  1866.         if (!$alreadyHashed) {
  1867.             $password Shared\PasswordHasher::hashPassword($password);
  1868.         }
  1869.         $this->protectedCells[$range] = $password;
  1870.         return $this;
  1871.     }
  1872.     /**
  1873.      * Set protection on a cell range by using numeric cell coordinates.
  1874.      *
  1875.      * @deprecated 1.23.0
  1876.      *      Use the protectCells() method with a cell address range such as 'C5:F8' instead;,
  1877.      *          or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1878.      *          or an AddressRange object.
  1879.      * @see Worksheet::protectCells()
  1880.      *
  1881.      * @param int $columnIndex1 Numeric column coordinate of the first cell
  1882.      * @param int $row1 Numeric row coordinate of the first cell
  1883.      * @param int $columnIndex2 Numeric column coordinate of the last cell
  1884.      * @param int $row2 Numeric row coordinate of the last cell
  1885.      * @param string $password Password to unlock the protection
  1886.      * @param bool $alreadyHashed If the password has already been hashed, set this to true
  1887.      *
  1888.      * @return $this
  1889.      */
  1890.     public function protectCellsByColumnAndRow($columnIndex1$row1$columnIndex2$row2$password$alreadyHashed false)
  1891.     {
  1892.         $cellRange = new CellRange(
  1893.             CellAddress::fromColumnAndRow($columnIndex1$row1),
  1894.             CellAddress::fromColumnAndRow($columnIndex2$row2)
  1895.         );
  1896.         return $this->protectCells($cellRange$password$alreadyHashed);
  1897.     }
  1898.     /**
  1899.      * Remove protection on a cell or cell range.
  1900.      *
  1901.      * @param AddressRange|array<int>|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10'
  1902.      *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1903.      *              or a CellAddress or AddressRange object.
  1904.      *
  1905.      * @return $this
  1906.      */
  1907.     public function unprotectCells($range)
  1908.     {
  1909.         $range Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range));
  1910.         if (isset($this->protectedCells[$range])) {
  1911.             unset($this->protectedCells[$range]);
  1912.         } else {
  1913.             throw new Exception('Cell range ' $range ' not known as protected.');
  1914.         }
  1915.         return $this;
  1916.     }
  1917.     /**
  1918.      * Remove protection on a cell range by using numeric cell coordinates.
  1919.      *
  1920.      * @deprecated 1.23.0
  1921.      *      Use the unprotectCells() method with a cell address range such as 'C5:F8' instead;,
  1922.      *          or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1923.      *          or an AddressRange object.
  1924.      * @see Worksheet::unprotectCells()
  1925.      *
  1926.      * @param int $columnIndex1 Numeric column coordinate of the first cell
  1927.      * @param int $row1 Numeric row coordinate of the first cell
  1928.      * @param int $columnIndex2 Numeric column coordinate of the last cell
  1929.      * @param int $row2 Numeric row coordinate of the last cell
  1930.      *
  1931.      * @return $this
  1932.      */
  1933.     public function unprotectCellsByColumnAndRow($columnIndex1$row1$columnIndex2$row2)
  1934.     {
  1935.         $cellRange = new CellRange(
  1936.             CellAddress::fromColumnAndRow($columnIndex1$row1),
  1937.             CellAddress::fromColumnAndRow($columnIndex2$row2)
  1938.         );
  1939.         return $this->unprotectCells($cellRange);
  1940.     }
  1941.     /**
  1942.      * Get protected cells.
  1943.      *
  1944.      * @return string[]
  1945.      */
  1946.     public function getProtectedCells()
  1947.     {
  1948.         return $this->protectedCells;
  1949.     }
  1950.     /**
  1951.      * Get Autofilter.
  1952.      *
  1953.      * @return AutoFilter
  1954.      */
  1955.     public function getAutoFilter()
  1956.     {
  1957.         return $this->autoFilter;
  1958.     }
  1959.     /**
  1960.      * Set AutoFilter.
  1961.      *
  1962.      * @param AddressRange|array<int>|AutoFilter|string $autoFilterOrRange
  1963.      *            A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility
  1964.      *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1965.      *              or an AddressRange.
  1966.      *
  1967.      * @return $this
  1968.      */
  1969.     public function setAutoFilter($autoFilterOrRange)
  1970.     {
  1971.         if (is_object($autoFilterOrRange) && ($autoFilterOrRange instanceof AutoFilter)) {
  1972.             $this->autoFilter $autoFilterOrRange;
  1973.         } else {
  1974.             $cellRange Functions::trimSheetFromCellReference(Validations::validateCellRange($autoFilterOrRange));
  1975.             $this->autoFilter->setRange($cellRange);
  1976.         }
  1977.         return $this;
  1978.     }
  1979.     /**
  1980.      * Set Autofilter Range by using numeric cell coordinates.
  1981.      *
  1982.      * @deprecated 1.23.0
  1983.      *      Use the setAutoFilter() method with a cell address range such as 'C5:F8' instead;,
  1984.      *          or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  1985.      *          or an AddressRange object or AutoFilter object.
  1986.      * @see Worksheet::setAutoFilter()
  1987.      *
  1988.      * @param int $columnIndex1 Numeric column coordinate of the first cell
  1989.      * @param int $row1 Numeric row coordinate of the first cell
  1990.      * @param int $columnIndex2 Numeric column coordinate of the second cell
  1991.      * @param int $row2 Numeric row coordinate of the second cell
  1992.      *
  1993.      * @return $this
  1994.      */
  1995.     public function setAutoFilterByColumnAndRow($columnIndex1$row1$columnIndex2$row2)
  1996.     {
  1997.         $cellRange = new CellRange(
  1998.             CellAddress::fromColumnAndRow($columnIndex1$row1),
  1999.             CellAddress::fromColumnAndRow($columnIndex2$row2)
  2000.         );
  2001.         return $this->setAutoFilter($cellRange);
  2002.     }
  2003.     /**
  2004.      * Remove autofilter.
  2005.      */
  2006.     public function removeAutoFilter(): self
  2007.     {
  2008.         $this->autoFilter->setRange('');
  2009.         return $this;
  2010.     }
  2011.     /**
  2012.      * Get collection of Tables.
  2013.      *
  2014.      * @return ArrayObject<int, Table>
  2015.      */
  2016.     public function getTableCollection()
  2017.     {
  2018.         return $this->tableCollection;
  2019.     }
  2020.     /**
  2021.      * Add Table.
  2022.      *
  2023.      * @return $this
  2024.      */
  2025.     public function addTable(Table $table): self
  2026.     {
  2027.         $table->setWorksheet($this);
  2028.         $this->tableCollection[] = $table;
  2029.         return $this;
  2030.     }
  2031.     /**
  2032.      * @return string[] array of Table names
  2033.      */
  2034.     public function getTableNames(): array
  2035.     {
  2036.         $tableNames = [];
  2037.         foreach ($this->tableCollection as $table) {
  2038.             /** @var Table $table */
  2039.             $tableNames[] = $table->getName();
  2040.         }
  2041.         return $tableNames;
  2042.     }
  2043.     /** @var null|Table */
  2044.     private static $scrutinizerNullTable;
  2045.     /** @var null|int */
  2046.     private static $scrutinizerNullInt;
  2047.     /**
  2048.      * @param string $name the table name to search
  2049.      *
  2050.      * @return null|Table The table from the tables collection, or null if not found
  2051.      */
  2052.     public function getTableByName(string $name): ?Table
  2053.     {
  2054.         $tableIndex $this->getTableIndexByName($name);
  2055.         return ($tableIndex === null) ? self::$scrutinizerNullTable $this->tableCollection[$tableIndex];
  2056.     }
  2057.     /**
  2058.      * @param string $name the table name to search
  2059.      *
  2060.      * @return null|int The index of the located table in the tables collection, or null if not found
  2061.      */
  2062.     protected function getTableIndexByName(string $name): ?int
  2063.     {
  2064.         $name Shared\StringHelper::strToUpper($name);
  2065.         foreach ($this->tableCollection as $index => $table) {
  2066.             /** @var Table $table */
  2067.             if (Shared\StringHelper::strToUpper($table->getName()) === $name) {
  2068.                 return $index;
  2069.             }
  2070.         }
  2071.         return self::$scrutinizerNullInt;
  2072.     }
  2073.     /**
  2074.      * Remove Table by name.
  2075.      *
  2076.      * @param string $name Table name
  2077.      *
  2078.      * @return $this
  2079.      */
  2080.     public function removeTableByName(string $name): self
  2081.     {
  2082.         $tableIndex $this->getTableIndexByName($name);
  2083.         if ($tableIndex !== null) {
  2084.             unset($this->tableCollection[$tableIndex]);
  2085.         }
  2086.         return $this;
  2087.     }
  2088.     /**
  2089.      * Remove collection of Tables.
  2090.      */
  2091.     public function removeTableCollection(): self
  2092.     {
  2093.         $this->tableCollection = new ArrayObject();
  2094.         return $this;
  2095.     }
  2096.     /**
  2097.      * Get Freeze Pane.
  2098.      *
  2099.      * @return null|string
  2100.      */
  2101.     public function getFreezePane()
  2102.     {
  2103.         return $this->freezePane;
  2104.     }
  2105.     /**
  2106.      * Freeze Pane.
  2107.      *
  2108.      * Examples:
  2109.      *
  2110.      *     - A2 will freeze the rows above cell A2 (i.e row 1)
  2111.      *     - B1 will freeze the columns to the left of cell B1 (i.e column A)
  2112.      *     - B2 will freeze the rows above and to the left of cell B2 (i.e row 1 and column A)
  2113.      *
  2114.      * @param null|array<int>|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5';
  2115.      *            or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  2116.      *        Passing a null value for this argument will clear any existing freeze pane for this worksheet.
  2117.      * @param null|array<int>|CellAddress|string $topLeftCell default position of the right bottom pane
  2118.      *            Coordinate of the cell as a string, eg: 'C5'; or as an array of [$columnIndex, $row] (e.g. [3, 5]),
  2119.      *            or a CellAddress object.
  2120.      *
  2121.      * @return $this
  2122.      */
  2123.     public function freezePane($coordinate$topLeftCell null)
  2124.     {
  2125.         $cellAddress = ($coordinate !== null)
  2126.             ? Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate))
  2127.             : null;
  2128.         if ($cellAddress !== null && Coordinate::coordinateIsRange($cellAddress)) {
  2129.             throw new Exception('Freeze pane can not be set on a range of cells.');
  2130.         }
  2131.         $topLeftCell = ($topLeftCell !== null)
  2132.             ? Functions::trimSheetFromCellReference(Validations::validateCellAddress($topLeftCell))
  2133.             : null;
  2134.         if ($cellAddress !== null && $topLeftCell === null) {
  2135.             $coordinate Coordinate::coordinateFromString($cellAddress);
  2136.             $topLeftCell $coordinate[0] . $coordinate[1];
  2137.         }
  2138.         $this->freezePane $cellAddress;
  2139.         $this->topLeftCell $topLeftCell;
  2140.         return $this;
  2141.     }
  2142.     public function setTopLeftCell(string $topLeftCell): self
  2143.     {
  2144.         $this->topLeftCell $topLeftCell;
  2145.         return $this;
  2146.     }
  2147.     /**
  2148.      * Freeze Pane by using numeric cell coordinates.
  2149.      *
  2150.      * @deprecated 1.23.0
  2151.      *      Use the freezePane() method with a cell address such as 'C5' instead;,
  2152.      *          or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  2153.      * @see Worksheet::freezePane()
  2154.      *
  2155.      * @param int $columnIndex Numeric column coordinate of the cell
  2156.      * @param int $row Numeric row coordinate of the cell
  2157.      *
  2158.      * @return $this
  2159.      */
  2160.     public function freezePaneByColumnAndRow($columnIndex$row)
  2161.     {
  2162.         return $this->freezePane(Coordinate::stringFromColumnIndex($columnIndex) . $row);
  2163.     }
  2164.     /**
  2165.      * Unfreeze Pane.
  2166.      *
  2167.      * @return $this
  2168.      */
  2169.     public function unfreezePane()
  2170.     {
  2171.         return $this->freezePane(null);
  2172.     }
  2173.     /**
  2174.      * Get the default position of the right bottom pane.
  2175.      *
  2176.      * @return null|string
  2177.      */
  2178.     public function getTopLeftCell()
  2179.     {
  2180.         return $this->topLeftCell;
  2181.     }
  2182.     /**
  2183.      * Insert a new row, updating all possible related data.
  2184.      *
  2185.      * @param int $before Insert before this row number
  2186.      * @param int $numberOfRows Number of new rows to insert
  2187.      *
  2188.      * @return $this
  2189.      */
  2190.     public function insertNewRowBefore(int $beforeint $numberOfRows 1)
  2191.     {
  2192.         if ($before >= 1) {
  2193.             $objReferenceHelper ReferenceHelper::getInstance();
  2194.             $objReferenceHelper->insertNewBefore('A' $before0$numberOfRows$this);
  2195.         } else {
  2196.             throw new Exception('Rows can only be inserted before at least row 1.');
  2197.         }
  2198.         return $this;
  2199.     }
  2200.     /**
  2201.      * Insert a new column, updating all possible related data.
  2202.      *
  2203.      * @param string $before Insert before this column Name, eg: 'A'
  2204.      * @param int $numberOfColumns Number of new columns to insert
  2205.      *
  2206.      * @return $this
  2207.      */
  2208.     public function insertNewColumnBefore(string $beforeint $numberOfColumns 1)
  2209.     {
  2210.         if (!is_numeric($before)) {
  2211.             $objReferenceHelper ReferenceHelper::getInstance();
  2212.             $objReferenceHelper->insertNewBefore($before '1'$numberOfColumns0$this);
  2213.         } else {
  2214.             throw new Exception('Column references should not be numeric.');
  2215.         }
  2216.         return $this;
  2217.     }
  2218.     /**
  2219.      * Insert a new column, updating all possible related data.
  2220.      *
  2221.      * @param int $beforeColumnIndex Insert before this column ID (numeric column coordinate of the cell)
  2222.      * @param int $numberOfColumns Number of new columns to insert
  2223.      *
  2224.      * @return $this
  2225.      */
  2226.     public function insertNewColumnBeforeByIndex(int $beforeColumnIndexint $numberOfColumns 1)
  2227.     {
  2228.         if ($beforeColumnIndex >= 1) {
  2229.             return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $numberOfColumns);
  2230.         }
  2231.         throw new Exception('Columns can only be inserted before at least column A (1).');
  2232.     }
  2233.     /**
  2234.      * Delete a row, updating all possible related data.
  2235.      *
  2236.      * @param int $row Remove rows, starting with this row number
  2237.      * @param int $numberOfRows Number of rows to remove
  2238.      *
  2239.      * @return $this
  2240.      */
  2241.     public function removeRow(int $rowint $numberOfRows 1)
  2242.     {
  2243.         if ($row 1) {
  2244.             throw new Exception('Rows to be deleted should at least start from row 1.');
  2245.         }
  2246.         $holdRowDimensions $this->removeRowDimensions($row$numberOfRows);
  2247.         $highestRow $this->getHighestDataRow();
  2248.         $removedRowsCounter 0;
  2249.         for ($r 0$r $numberOfRows; ++$r) {
  2250.             if ($row $r <= $highestRow) {
  2251.                 $this->getCellCollection()->removeRow($row $r);
  2252.                 ++$removedRowsCounter;
  2253.             }
  2254.         }
  2255.         $objReferenceHelper ReferenceHelper::getInstance();
  2256.         $objReferenceHelper->insertNewBefore('A' . ($row $numberOfRows), 0, -$numberOfRows$this);
  2257.         for ($r 0$r $removedRowsCounter; ++$r) {
  2258.             $this->getCellCollection()->removeRow($highestRow);
  2259.             --$highestRow;
  2260.         }
  2261.         $this->rowDimensions $holdRowDimensions;
  2262.         return $this;
  2263.     }
  2264.     private function removeRowDimensions(int $rowint $numberOfRows): array
  2265.     {
  2266.         $highRow $row $numberOfRows 1;
  2267.         $holdRowDimensions = [];
  2268.         foreach ($this->rowDimensions as $rowDimension) {
  2269.             $num $rowDimension->getRowIndex();
  2270.             if ($num $row) {
  2271.                 $holdRowDimensions[$num] = $rowDimension;
  2272.             } elseif ($num $highRow) {
  2273.                 $num -= $numberOfRows;
  2274.                 $cloneDimension = clone $rowDimension;
  2275.                 $cloneDimension->setRowIndex(/** @scrutinizer ignore-type */ $num);
  2276.                 $holdRowDimensions[$num] = $cloneDimension;
  2277.             }
  2278.         }
  2279.         return $holdRowDimensions;
  2280.     }
  2281.     /**
  2282.      * Remove a column, updating all possible related data.
  2283.      *
  2284.      * @param string $column Remove columns starting with this column name, eg: 'A'
  2285.      * @param int $numberOfColumns Number of columns to remove
  2286.      *
  2287.      * @return $this
  2288.      */
  2289.     public function removeColumn(string $columnint $numberOfColumns 1)
  2290.     {
  2291.         if (is_numeric($column)) {
  2292.             throw new Exception('Column references should not be numeric.');
  2293.         }
  2294.         $highestColumn $this->getHighestDataColumn();
  2295.         $highestColumnIndex Coordinate::columnIndexFromString($highestColumn);
  2296.         $pColumnIndex Coordinate::columnIndexFromString($column);
  2297.         $holdColumnDimensions $this->removeColumnDimensions($pColumnIndex$numberOfColumns);
  2298.         $column Coordinate::stringFromColumnIndex($pColumnIndex $numberOfColumns);
  2299.         $objReferenceHelper ReferenceHelper::getInstance();
  2300.         $objReferenceHelper->insertNewBefore($column '1', -$numberOfColumns0$this);
  2301.         $this->columnDimensions $holdColumnDimensions;
  2302.         if ($pColumnIndex $highestColumnIndex) {
  2303.             return $this;
  2304.         }
  2305.         $maxPossibleColumnsToBeRemoved $highestColumnIndex $pColumnIndex 1;
  2306.         for ($c 0$n min($maxPossibleColumnsToBeRemoved$numberOfColumns); $c $n; ++$c) {
  2307.             $this->getCellCollection()->removeColumn($highestColumn);
  2308.             $highestColumn Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1);
  2309.         }
  2310.         $this->garbageCollect();
  2311.         return $this;
  2312.     }
  2313.     private function removeColumnDimensions(int $pColumnIndexint $numberOfColumns): array
  2314.     {
  2315.         $highCol $pColumnIndex $numberOfColumns 1;
  2316.         $holdColumnDimensions = [];
  2317.         foreach ($this->columnDimensions as $columnDimension) {
  2318.             $num $columnDimension->getColumnNumeric();
  2319.             if ($num $pColumnIndex) {
  2320.                 $str $columnDimension->getColumnIndex();
  2321.                 $holdColumnDimensions[$str] = $columnDimension;
  2322.             } elseif ($num $highCol) {
  2323.                 $cloneDimension = clone $columnDimension;
  2324.                 $cloneDimension->setColumnNumeric($num $numberOfColumns);
  2325.                 $str $cloneDimension->getColumnIndex();
  2326.                 $holdColumnDimensions[$str] = $cloneDimension;
  2327.             }
  2328.         }
  2329.         return $holdColumnDimensions;
  2330.     }
  2331.     /**
  2332.      * Remove a column, updating all possible related data.
  2333.      *
  2334.      * @param int $columnIndex Remove starting with this column Index (numeric column coordinate)
  2335.      * @param int $numColumns Number of columns to remove
  2336.      *
  2337.      * @return $this
  2338.      */
  2339.     public function removeColumnByIndex(int $columnIndexint $numColumns 1)
  2340.     {
  2341.         if ($columnIndex >= 1) {
  2342.             return $this->removeColumn(Coordinate::stringFromColumnIndex($columnIndex), $numColumns);
  2343.         }
  2344.         throw new Exception('Columns to be deleted should at least start from column A (1)');
  2345.     }
  2346.     /**
  2347.      * Show gridlines?
  2348.      */
  2349.     public function getShowGridlines(): bool
  2350.     {
  2351.         return $this->showGridlines;
  2352.     }
  2353.     /**
  2354.      * Set show gridlines.
  2355.      *
  2356.      * @param bool $showGridLines Show gridlines (true/false)
  2357.      *
  2358.      * @return $this
  2359.      */
  2360.     public function setShowGridlines(bool $showGridLines): self
  2361.     {
  2362.         $this->showGridlines $showGridLines;
  2363.         return $this;
  2364.     }
  2365.     /**
  2366.      * Print gridlines?
  2367.      */
  2368.     public function getPrintGridlines(): bool
  2369.     {
  2370.         return $this->printGridlines;
  2371.     }
  2372.     /**
  2373.      * Set print gridlines.
  2374.      *
  2375.      * @param bool $printGridLines Print gridlines (true/false)
  2376.      *
  2377.      * @return $this
  2378.      */
  2379.     public function setPrintGridlines(bool $printGridLines): self
  2380.     {
  2381.         $this->printGridlines $printGridLines;
  2382.         return $this;
  2383.     }
  2384.     /**
  2385.      * Show row and column headers?
  2386.      */
  2387.     public function getShowRowColHeaders(): bool
  2388.     {
  2389.         return $this->showRowColHeaders;
  2390.     }
  2391.     /**
  2392.      * Set show row and column headers.
  2393.      *
  2394.      * @param bool $showRowColHeaders Show row and column headers (true/false)
  2395.      *
  2396.      * @return $this
  2397.      */
  2398.     public function setShowRowColHeaders(bool $showRowColHeaders): self
  2399.     {
  2400.         $this->showRowColHeaders $showRowColHeaders;
  2401.         return $this;
  2402.     }
  2403.     /**
  2404.      * Show summary below? (Row/Column outlining).
  2405.      */
  2406.     public function getShowSummaryBelow(): bool
  2407.     {
  2408.         return $this->showSummaryBelow;
  2409.     }
  2410.     /**
  2411.      * Set show summary below.
  2412.      *
  2413.      * @param bool $showSummaryBelow Show summary below (true/false)
  2414.      *
  2415.      * @return $this
  2416.      */
  2417.     public function setShowSummaryBelow(bool $showSummaryBelow): self
  2418.     {
  2419.         $this->showSummaryBelow $showSummaryBelow;
  2420.         return $this;
  2421.     }
  2422.     /**
  2423.      * Show summary right? (Row/Column outlining).
  2424.      */
  2425.     public function getShowSummaryRight(): bool
  2426.     {
  2427.         return $this->showSummaryRight;
  2428.     }
  2429.     /**
  2430.      * Set show summary right.
  2431.      *
  2432.      * @param bool $showSummaryRight Show summary right (true/false)
  2433.      *
  2434.      * @return $this
  2435.      */
  2436.     public function setShowSummaryRight(bool $showSummaryRight): self
  2437.     {
  2438.         $this->showSummaryRight $showSummaryRight;
  2439.         return $this;
  2440.     }
  2441.     /**
  2442.      * Get comments.
  2443.      *
  2444.      * @return Comment[]
  2445.      */
  2446.     public function getComments()
  2447.     {
  2448.         return $this->comments;
  2449.     }
  2450.     /**
  2451.      * Set comments array for the entire sheet.
  2452.      *
  2453.      * @param Comment[] $comments
  2454.      *
  2455.      * @return $this
  2456.      */
  2457.     public function setComments(array $comments): self
  2458.     {
  2459.         $this->comments $comments;
  2460.         return $this;
  2461.     }
  2462.     /**
  2463.      * Remove comment from cell.
  2464.      *
  2465.      * @param array<int>|CellAddress|string $cellCoordinate Coordinate of the cell as a string, eg: 'C5';
  2466.      *               or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  2467.      *
  2468.      * @return $this
  2469.      */
  2470.     public function removeComment($cellCoordinate): self
  2471.     {
  2472.         $cellAddress Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate));
  2473.         if (Coordinate::coordinateIsRange($cellAddress)) {
  2474.             throw new Exception('Cell coordinate string can not be a range of cells.');
  2475.         } elseif (strpos($cellAddress'$') !== false) {
  2476.             throw new Exception('Cell coordinate string must not be absolute.');
  2477.         } elseif ($cellAddress == '') {
  2478.             throw new Exception('Cell coordinate can not be zero-length string.');
  2479.         }
  2480.         // Check if we have a comment for this cell and delete it
  2481.         if (isset($this->comments[$cellAddress])) {
  2482.             unset($this->comments[$cellAddress]);
  2483.         }
  2484.         return $this;
  2485.     }
  2486.     /**
  2487.      * Get comment for cell.
  2488.      *
  2489.      * @param array<int>|CellAddress|string $cellCoordinate Coordinate of the cell as a string, eg: 'C5';
  2490.      *               or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  2491.      */
  2492.     public function getComment($cellCoordinate): Comment
  2493.     {
  2494.         $cellAddress Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate));
  2495.         if (Coordinate::coordinateIsRange($cellAddress)) {
  2496.             throw new Exception('Cell coordinate string can not be a range of cells.');
  2497.         } elseif (strpos($cellAddress'$') !== false) {
  2498.             throw new Exception('Cell coordinate string must not be absolute.');
  2499.         } elseif ($cellAddress == '') {
  2500.             throw new Exception('Cell coordinate can not be zero-length string.');
  2501.         }
  2502.         // Check if we already have a comment for this cell.
  2503.         if (isset($this->comments[$cellAddress])) {
  2504.             return $this->comments[$cellAddress];
  2505.         }
  2506.         // If not, create a new comment.
  2507.         $newComment = new Comment();
  2508.         $this->comments[$cellAddress] = $newComment;
  2509.         return $newComment;
  2510.     }
  2511.     /**
  2512.      * Get comment for cell by using numeric cell coordinates.
  2513.      *
  2514.      * @deprecated 1.23.0
  2515.      *      Use the getComment() method with a cell address such as 'C5' instead;,
  2516.      *          or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  2517.      * @see Worksheet::getComment()
  2518.      *
  2519.      * @param int $columnIndex Numeric column coordinate of the cell
  2520.      * @param int $row Numeric row coordinate of the cell
  2521.      */
  2522.     public function getCommentByColumnAndRow($columnIndex$row): Comment
  2523.     {
  2524.         return $this->getComment(Coordinate::stringFromColumnIndex($columnIndex) . $row);
  2525.     }
  2526.     /**
  2527.      * Get active cell.
  2528.      *
  2529.      * @return string Example: 'A1'
  2530.      */
  2531.     public function getActiveCell()
  2532.     {
  2533.         return $this->activeCell;
  2534.     }
  2535.     /**
  2536.      * Get selected cells.
  2537.      *
  2538.      * @return string
  2539.      */
  2540.     public function getSelectedCells()
  2541.     {
  2542.         return $this->selectedCells;
  2543.     }
  2544.     /**
  2545.      * Selected cell.
  2546.      *
  2547.      * @param string $coordinate Cell (i.e. A1)
  2548.      *
  2549.      * @return $this
  2550.      */
  2551.     public function setSelectedCell($coordinate)
  2552.     {
  2553.         return $this->setSelectedCells($coordinate);
  2554.     }
  2555.     /**
  2556.      * Select a range of cells.
  2557.      *
  2558.      * @param AddressRange|array<int>|CellAddress|int|string $coordinate A simple string containing a Cell range like 'A1:E10'
  2559.      *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  2560.      *              or a CellAddress or AddressRange object.
  2561.      *
  2562.      * @return $this
  2563.      */
  2564.     public function setSelectedCells($coordinate)
  2565.     {
  2566.         if (is_string($coordinate)) {
  2567.             $coordinate Validations::definedNameToCoordinate($coordinate$this);
  2568.         }
  2569.         $coordinate Validations::validateCellOrCellRange($coordinate);
  2570.         if (Coordinate::coordinateIsRange($coordinate)) {
  2571.             [$first] = Coordinate::splitRange($coordinate);
  2572.             $this->activeCell $first[0];
  2573.         } else {
  2574.             $this->activeCell $coordinate;
  2575.         }
  2576.         $this->selectedCells $coordinate;
  2577.         return $this;
  2578.     }
  2579.     /**
  2580.      * Selected cell by using numeric cell coordinates.
  2581.      *
  2582.      * @deprecated 1.23.0
  2583.      *      Use the setSelectedCells() method with a cell address such as 'C5' instead;,
  2584.      *          or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
  2585.      * @see Worksheet::setSelectedCells()
  2586.      *
  2587.      * @param int $columnIndex Numeric column coordinate of the cell
  2588.      * @param int $row Numeric row coordinate of the cell
  2589.      *
  2590.      * @return $this
  2591.      */
  2592.     public function setSelectedCellByColumnAndRow($columnIndex$row)
  2593.     {
  2594.         return $this->setSelectedCells(Coordinate::stringFromColumnIndex($columnIndex) . $row);
  2595.     }
  2596.     /**
  2597.      * Get right-to-left.
  2598.      *
  2599.      * @return bool
  2600.      */
  2601.     public function getRightToLeft()
  2602.     {
  2603.         return $this->rightToLeft;
  2604.     }
  2605.     /**
  2606.      * Set right-to-left.
  2607.      *
  2608.      * @param bool $value Right-to-left true/false
  2609.      *
  2610.      * @return $this
  2611.      */
  2612.     public function setRightToLeft($value)
  2613.     {
  2614.         $this->rightToLeft $value;
  2615.         return $this;
  2616.     }
  2617.     /**
  2618.      * Fill worksheet from values in array.
  2619.      *
  2620.      * @param array $source Source array
  2621.      * @param mixed $nullValue Value in source array that stands for blank cell
  2622.      * @param string $startCell Insert array starting from this cell address as the top left coordinate
  2623.      * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array
  2624.      *
  2625.      * @return $this
  2626.      */
  2627.     public function fromArray(array $source$nullValue null$startCell 'A1'$strictNullComparison false)
  2628.     {
  2629.         //    Convert a 1-D array to 2-D (for ease of looping)
  2630.         if (!is_array(end($source))) {
  2631.             $source = [$source];
  2632.         }
  2633.         // start coordinate
  2634.         [$startColumn$startRow] = Coordinate::coordinateFromString($startCell);
  2635.         // Loop through $source
  2636.         foreach ($source as $rowData) {
  2637.             $currentColumn $startColumn;
  2638.             foreach ($rowData as $cellValue) {
  2639.                 if ($strictNullComparison) {
  2640.                     if ($cellValue !== $nullValue) {
  2641.                         // Set cell value
  2642.                         $this->getCell($currentColumn $startRow)->setValue($cellValue);
  2643.                     }
  2644.                 } else {
  2645.                     if ($cellValue != $nullValue) {
  2646.                         // Set cell value
  2647.                         $this->getCell($currentColumn $startRow)->setValue($cellValue);
  2648.                     }
  2649.                 }
  2650.                 ++$currentColumn;
  2651.             }
  2652.             ++$startRow;
  2653.         }
  2654.         return $this;
  2655.     }
  2656.     /**
  2657.      * @param mixed $nullValue
  2658.      *
  2659.      * @throws Exception
  2660.      * @throws \PhpOffice\PhpSpreadsheet\Calculation\Exception
  2661.      *
  2662.      * @return mixed
  2663.      */
  2664.     protected function cellToArray(Cell $cellbool $calculateFormulasbool $formatData$nullValue)
  2665.     {
  2666.         $returnValue $nullValue;
  2667.         if ($cell->getValue() !== null) {
  2668.             if ($cell->getValue() instanceof RichText) {
  2669.                 $returnValue $cell->getValue()->getPlainText();
  2670.             } else {
  2671.                 $returnValue = ($calculateFormulas) ? $cell->getCalculatedValue() : $cell->getValue();
  2672.             }
  2673.             if ($formatData) {
  2674.                 $style $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex());
  2675.                 $returnValue NumberFormat::toFormattedString(
  2676.                     $returnValue,
  2677.                     $style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL
  2678.                 );
  2679.             }
  2680.         }
  2681.         return $returnValue;
  2682.     }
  2683.     /**
  2684.      * Create array from a range of cells.
  2685.      *
  2686.      * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
  2687.      * @param bool $calculateFormulas Should formulas be calculated?
  2688.      * @param bool $formatData Should formatting be applied to cell values?
  2689.      * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
  2690.      *                             True - Return rows and columns indexed by their actual row and column IDs
  2691.      * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
  2692.      *                            True - Don't return values for rows/columns that are defined as hidden.
  2693.      */
  2694.     public function rangeToArray(
  2695.         string $range,
  2696.         $nullValue null,
  2697.         bool $calculateFormulas true,
  2698.         bool $formatData true,
  2699.         bool $returnCellRef false,
  2700.         bool $ignoreHidden false
  2701.     ): array {
  2702.         $range Validations::validateCellOrCellRange($range);
  2703.         $returnValue = [];
  2704.         //    Identify the range that we need to extract from the worksheet
  2705.         [$rangeStart$rangeEnd] = Coordinate::rangeBoundaries($range);
  2706.         $minCol Coordinate::stringFromColumnIndex($rangeStart[0]);
  2707.         $minRow $rangeStart[1];
  2708.         $maxCol Coordinate::stringFromColumnIndex($rangeEnd[0]);
  2709.         $maxRow $rangeEnd[1];
  2710.         ++$maxCol;
  2711.         // Loop through rows
  2712.         $r = -1;
  2713.         for ($row $minRow$row <= $maxRow; ++$row) {
  2714.             if (($ignoreHidden === true) && ($this->getRowDimension($row)->getVisible() === false)) {
  2715.                 continue;
  2716.             }
  2717.             $rowRef $returnCellRef $row : ++$r;
  2718.             $c = -1;
  2719.             // Loop through columns in the current row
  2720.             for ($col $minCol$col !== $maxCol; ++$col) {
  2721.                 if (($ignoreHidden === true) && ($this->getColumnDimension($col)->getVisible() === false)) {
  2722.                     continue;
  2723.                 }
  2724.                 $columnRef $returnCellRef $col : ++$c;
  2725.                 //    Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
  2726.                 //        so we test and retrieve directly against cellCollection
  2727.                 $cell $this->cellCollection->get("{$col}{$row}");
  2728.                 $returnValue[$rowRef][$columnRef] = $nullValue;
  2729.                 if ($cell !== null) {
  2730.                     $returnValue[$rowRef][$columnRef] = $this->cellToArray($cell$calculateFormulas$formatData$nullValue);
  2731.                 }
  2732.             }
  2733.         }
  2734.         // Return
  2735.         return $returnValue;
  2736.     }
  2737.     private function validateNamedRange(string $definedNamebool $returnNullIfInvalid false): ?DefinedName
  2738.     {
  2739.         $namedRange DefinedName::resolveName($definedName$this);
  2740.         if ($namedRange === null) {
  2741.             if ($returnNullIfInvalid) {
  2742.                 return null;
  2743.             }
  2744.             throw new Exception('Named Range ' $definedName ' does not exist.');
  2745.         }
  2746.         if ($namedRange->isFormula()) {
  2747.             if ($returnNullIfInvalid) {
  2748.                 return null;
  2749.             }
  2750.             throw new Exception('Defined Named ' $definedName ' is a formula, not a range or cell.');
  2751.         }
  2752.         if ($namedRange->getLocalOnly()) {
  2753.             $worksheet $namedRange->getWorksheet();
  2754.             if ($worksheet === null || $this->getHashCode() !== $worksheet->getHashCode()) {
  2755.                 if ($returnNullIfInvalid) {
  2756.                     return null;
  2757.                 }
  2758.                 throw new Exception(
  2759.                     'Named range ' $definedName ' is not accessible from within sheet ' $this->getTitle()
  2760.                 );
  2761.             }
  2762.         }
  2763.         return $namedRange;
  2764.     }
  2765.     /**
  2766.      * Create array from a range of cells.
  2767.      *
  2768.      * @param string $definedName The Named Range that should be returned
  2769.      * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
  2770.      * @param bool $calculateFormulas Should formulas be calculated?
  2771.      * @param bool $formatData Should formatting be applied to cell values?
  2772.      * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
  2773.      *                             True - Return rows and columns indexed by their actual row and column IDs
  2774.      * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
  2775.      *                            True - Don't return values for rows/columns that are defined as hidden.
  2776.      */
  2777.     public function namedRangeToArray(
  2778.         string $definedName,
  2779.         $nullValue null,
  2780.         bool $calculateFormulas true,
  2781.         bool $formatData true,
  2782.         bool $returnCellRef false,
  2783.         bool $ignoreHidden false
  2784.     ): array {
  2785.         $retVal = [];
  2786.         $namedRange $this->validateNamedRange($definedName);
  2787.         if ($namedRange !== null) {
  2788.             $cellRange ltrim(substr($namedRange->getValue(), (int) strrpos($namedRange->getValue(), '!')), '!');
  2789.             $cellRange str_replace('$'''$cellRange);
  2790.             $workSheet $namedRange->getWorksheet();
  2791.             if ($workSheet !== null) {
  2792.                 $retVal $workSheet->rangeToArray($cellRange$nullValue$calculateFormulas$formatData$returnCellRef$ignoreHidden);
  2793.             }
  2794.         }
  2795.         return $retVal;
  2796.     }
  2797.     /**
  2798.      * Create array from worksheet.
  2799.      *
  2800.      * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
  2801.      * @param bool $calculateFormulas Should formulas be calculated?
  2802.      * @param bool $formatData Should formatting be applied to cell values?
  2803.      * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
  2804.      *                             True - Return rows and columns indexed by their actual row and column IDs
  2805.      * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
  2806.      *                            True - Don't return values for rows/columns that are defined as hidden.
  2807.      */
  2808.     public function toArray(
  2809.         $nullValue null,
  2810.         bool $calculateFormulas true,
  2811.         bool $formatData true,
  2812.         bool $returnCellRef false,
  2813.         bool $ignoreHidden false
  2814.     ): array {
  2815.         // Garbage collect...
  2816.         $this->garbageCollect();
  2817.         //    Identify the range that we need to extract from the worksheet
  2818.         $maxCol $this->getHighestColumn();
  2819.         $maxRow $this->getHighestRow();
  2820.         // Return
  2821.         return $this->rangeToArray("A1:{$maxCol}{$maxRow}"$nullValue$calculateFormulas$formatData$returnCellRef$ignoreHidden);
  2822.     }
  2823.     /**
  2824.      * Get row iterator.
  2825.      *
  2826.      * @param int $startRow The row number at which to start iterating
  2827.      * @param int $endRow The row number at which to stop iterating
  2828.      *
  2829.      * @return RowIterator
  2830.      */
  2831.     public function getRowIterator($startRow 1$endRow null)
  2832.     {
  2833.         return new RowIterator($this$startRow$endRow);
  2834.     }
  2835.     /**
  2836.      * Get column iterator.
  2837.      *
  2838.      * @param string $startColumn The column address at which to start iterating
  2839.      * @param string $endColumn The column address at which to stop iterating
  2840.      *
  2841.      * @return ColumnIterator
  2842.      */
  2843.     public function getColumnIterator($startColumn 'A'$endColumn null)
  2844.     {
  2845.         return new ColumnIterator($this$startColumn$endColumn);
  2846.     }
  2847.     /**
  2848.      * Run PhpSpreadsheet garbage collector.
  2849.      *
  2850.      * @return $this
  2851.      */
  2852.     public function garbageCollect()
  2853.     {
  2854.         // Flush cache
  2855.         $this->cellCollection->get('A1');
  2856.         // Lookup highest column and highest row if cells are cleaned
  2857.         $colRow $this->cellCollection->getHighestRowAndColumn();
  2858.         $highestRow $colRow['row'];
  2859.         $highestColumn Coordinate::columnIndexFromString($colRow['column']);
  2860.         // Loop through column dimensions
  2861.         foreach ($this->columnDimensions as $dimension) {
  2862.             $highestColumn max($highestColumnCoordinate::columnIndexFromString($dimension->getColumnIndex()));
  2863.         }
  2864.         // Loop through row dimensions
  2865.         foreach ($this->rowDimensions as $dimension) {
  2866.             $highestRow max($highestRow$dimension->getRowIndex());
  2867.         }
  2868.         // Cache values
  2869.         if ($highestColumn 1) {
  2870.             $this->cachedHighestColumn 1;
  2871.         } else {
  2872.             $this->cachedHighestColumn $highestColumn;
  2873.         }
  2874.         $this->cachedHighestRow $highestRow;
  2875.         // Return
  2876.         return $this;
  2877.     }
  2878.     /**
  2879.      * Get hash code.
  2880.      *
  2881.      * @return string Hash code
  2882.      */
  2883.     public function getHashCode()
  2884.     {
  2885.         if ($this->dirty) {
  2886.             $this->hash md5($this->title $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' 'f') . __CLASS__);
  2887.             $this->dirty false;
  2888.         }
  2889.         return $this->hash;
  2890.     }
  2891.     /**
  2892.      * Extract worksheet title from range.
  2893.      *
  2894.      * Example: extractSheetTitle("testSheet!A1") ==> 'A1'
  2895.      * Example: extractSheetTitle("testSheet!A1:C3") ==> 'A1:C3'
  2896.      * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> ['testSheet 1', 'A1'];
  2897.      * Example: extractSheetTitle("'testSheet 1'!A1:C3", true) ==> ['testSheet 1', 'A1:C3'];
  2898.      * Example: extractSheetTitle("A1", true) ==> ['', 'A1'];
  2899.      * Example: extractSheetTitle("A1:C3", true) ==> ['', 'A1:C3']
  2900.      *
  2901.      * @param string $range Range to extract title from
  2902.      * @param bool $returnRange Return range? (see example)
  2903.      *
  2904.      * @return mixed
  2905.      */
  2906.     public static function extractSheetTitle($range$returnRange false)
  2907.     {
  2908.         if (empty($range)) {
  2909.             return $returnRange ? [nullnull] : null;
  2910.         }
  2911.         // Sheet title included?
  2912.         if (($sep strrpos($range'!')) === false) {
  2913.             return $returnRange ? [''$range] : '';
  2914.         }
  2915.         if ($returnRange) {
  2916.             return [substr($range0$sep), substr($range$sep 1)];
  2917.         }
  2918.         return substr($range$sep 1);
  2919.     }
  2920.     /**
  2921.      * Get hyperlink.
  2922.      *
  2923.      * @param string $cellCoordinate Cell coordinate to get hyperlink for, eg: 'A1'
  2924.      *
  2925.      * @return Hyperlink
  2926.      */
  2927.     public function getHyperlink($cellCoordinate)
  2928.     {
  2929.         // return hyperlink if we already have one
  2930.         if (isset($this->hyperlinkCollection[$cellCoordinate])) {
  2931.             return $this->hyperlinkCollection[$cellCoordinate];
  2932.         }
  2933.         // else create hyperlink
  2934.         $this->hyperlinkCollection[$cellCoordinate] = new Hyperlink();
  2935.         return $this->hyperlinkCollection[$cellCoordinate];
  2936.     }
  2937.     /**
  2938.      * Set hyperlink.
  2939.      *
  2940.      * @param string $cellCoordinate Cell coordinate to insert hyperlink, eg: 'A1'
  2941.      *
  2942.      * @return $this
  2943.      */
  2944.     public function setHyperlink($cellCoordinate, ?Hyperlink $hyperlink null)
  2945.     {
  2946.         if ($hyperlink === null) {
  2947.             unset($this->hyperlinkCollection[$cellCoordinate]);
  2948.         } else {
  2949.             $this->hyperlinkCollection[$cellCoordinate] = $hyperlink;
  2950.         }
  2951.         return $this;
  2952.     }
  2953.     /**
  2954.      * Hyperlink at a specific coordinate exists?
  2955.      *
  2956.      * @param string $coordinate eg: 'A1'
  2957.      *
  2958.      * @return bool
  2959.      */
  2960.     public function hyperlinkExists($coordinate)
  2961.     {
  2962.         return isset($this->hyperlinkCollection[$coordinate]);
  2963.     }
  2964.     /**
  2965.      * Get collection of hyperlinks.
  2966.      *
  2967.      * @return Hyperlink[]
  2968.      */
  2969.     public function getHyperlinkCollection()
  2970.     {
  2971.         return $this->hyperlinkCollection;
  2972.     }
  2973.     /**
  2974.      * Get data validation.
  2975.      *
  2976.      * @param string $cellCoordinate Cell coordinate to get data validation for, eg: 'A1'
  2977.      *
  2978.      * @return DataValidation
  2979.      */
  2980.     public function getDataValidation($cellCoordinate)
  2981.     {
  2982.         // return data validation if we already have one
  2983.         if (isset($this->dataValidationCollection[$cellCoordinate])) {
  2984.             return $this->dataValidationCollection[$cellCoordinate];
  2985.         }
  2986.         // else create data validation
  2987.         $this->dataValidationCollection[$cellCoordinate] = new DataValidation();
  2988.         return $this->dataValidationCollection[$cellCoordinate];
  2989.     }
  2990.     /**
  2991.      * Set data validation.
  2992.      *
  2993.      * @param string $cellCoordinate Cell coordinate to insert data validation, eg: 'A1'
  2994.      *
  2995.      * @return $this
  2996.      */
  2997.     public function setDataValidation($cellCoordinate, ?DataValidation $dataValidation null)
  2998.     {
  2999.         if ($dataValidation === null) {
  3000.             unset($this->dataValidationCollection[$cellCoordinate]);
  3001.         } else {
  3002.             $this->dataValidationCollection[$cellCoordinate] = $dataValidation;
  3003.         }
  3004.         return $this;
  3005.     }
  3006.     /**
  3007.      * Data validation at a specific coordinate exists?
  3008.      *
  3009.      * @param string $coordinate eg: 'A1'
  3010.      *
  3011.      * @return bool
  3012.      */
  3013.     public function dataValidationExists($coordinate)
  3014.     {
  3015.         return isset($this->dataValidationCollection[$coordinate]);
  3016.     }
  3017.     /**
  3018.      * Get collection of data validations.
  3019.      *
  3020.      * @return DataValidation[]
  3021.      */
  3022.     public function getDataValidationCollection()
  3023.     {
  3024.         return $this->dataValidationCollection;
  3025.     }
  3026.     /**
  3027.      * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet.
  3028.      *
  3029.      * @param string $range
  3030.      *
  3031.      * @return string Adjusted range value
  3032.      */
  3033.     public function shrinkRangeToFit($range)
  3034.     {
  3035.         $maxCol $this->getHighestColumn();
  3036.         $maxRow $this->getHighestRow();
  3037.         $maxCol Coordinate::columnIndexFromString($maxCol);
  3038.         $rangeBlocks explode(' '$range);
  3039.         foreach ($rangeBlocks as &$rangeSet) {
  3040.             $rangeBoundaries Coordinate::getRangeBoundaries($rangeSet);
  3041.             if (Coordinate::columnIndexFromString($rangeBoundaries[0][0]) > $maxCol) {
  3042.                 $rangeBoundaries[0][0] = Coordinate::stringFromColumnIndex($maxCol);
  3043.             }
  3044.             if ($rangeBoundaries[0][1] > $maxRow) {
  3045.                 $rangeBoundaries[0][1] = $maxRow;
  3046.             }
  3047.             if (Coordinate::columnIndexFromString($rangeBoundaries[1][0]) > $maxCol) {
  3048.                 $rangeBoundaries[1][0] = Coordinate::stringFromColumnIndex($maxCol);
  3049.             }
  3050.             if ($rangeBoundaries[1][1] > $maxRow) {
  3051.                 $rangeBoundaries[1][1] = $maxRow;
  3052.             }
  3053.             $rangeSet $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' $rangeBoundaries[1][0] . $rangeBoundaries[1][1];
  3054.         }
  3055.         unset($rangeSet);
  3056.         return implode(' '$rangeBlocks);
  3057.     }
  3058.     /**
  3059.      * Get tab color.
  3060.      *
  3061.      * @return Color
  3062.      */
  3063.     public function getTabColor()
  3064.     {
  3065.         if ($this->tabColor === null) {
  3066.             $this->tabColor = new Color();
  3067.         }
  3068.         return $this->tabColor;
  3069.     }
  3070.     /**
  3071.      * Reset tab color.
  3072.      *
  3073.      * @return $this
  3074.      */
  3075.     public function resetTabColor()
  3076.     {
  3077.         $this->tabColor null;
  3078.         return $this;
  3079.     }
  3080.     /**
  3081.      * Tab color set?
  3082.      *
  3083.      * @return bool
  3084.      */
  3085.     public function isTabColorSet()
  3086.     {
  3087.         return $this->tabColor !== null;
  3088.     }
  3089.     /**
  3090.      * Copy worksheet (!= clone!).
  3091.      *
  3092.      * @return static
  3093.      */
  3094.     public function copy()
  3095.     {
  3096.         return clone $this;
  3097.     }
  3098.     /**
  3099.      * Returns a boolean true if the specified row contains no cells. By default, this means that no cell records
  3100.      *          exist in the collection for this row. false will be returned otherwise.
  3101.      *     This rule can be modified by passing a $definitionOfEmptyFlags value:
  3102.      *          1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value
  3103.      *                  cells, then the row will be considered empty.
  3104.      *          2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty
  3105.      *                  string value cells, then the row will be considered empty.
  3106.      *          3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL
  3107.      *                  If the only cells in the collection are null value or empty string value cells, then the row
  3108.      *                  will be considered empty.
  3109.      *
  3110.      * @param int $definitionOfEmptyFlags
  3111.      *              Possible Flag Values are:
  3112.      *                  CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL
  3113.      *                  CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL
  3114.      */
  3115.     public function isEmptyRow(int $rowIdint $definitionOfEmptyFlags 0): bool
  3116.     {
  3117.         try {
  3118.             $iterator = new RowIterator($this$rowId$rowId);
  3119.             $iterator->seek($rowId);
  3120.             $row $iterator->current();
  3121.         } catch (Exception $e) {
  3122.             return true;
  3123.         }
  3124.         return $row->isEmpty($definitionOfEmptyFlags);
  3125.     }
  3126.     /**
  3127.      * Returns a boolean true if the specified column contains no cells. By default, this means that no cell records
  3128.      *          exist in the collection for this column. false will be returned otherwise.
  3129.      *     This rule can be modified by passing a $definitionOfEmptyFlags value:
  3130.      *          1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value
  3131.      *                  cells, then the column will be considered empty.
  3132.      *          2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty
  3133.      *                  string value cells, then the column will be considered empty.
  3134.      *          3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL
  3135.      *                  If the only cells in the collection are null value or empty string value cells, then the column
  3136.      *                  will be considered empty.
  3137.      *
  3138.      * @param int $definitionOfEmptyFlags
  3139.      *              Possible Flag Values are:
  3140.      *                  CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL
  3141.      *                  CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL
  3142.      */
  3143.     public function isEmptyColumn(string $columnIdint $definitionOfEmptyFlags 0): bool
  3144.     {
  3145.         try {
  3146.             $iterator = new ColumnIterator($this$columnId$columnId);
  3147.             $iterator->seek($columnId);
  3148.             $column $iterator->current();
  3149.         } catch (Exception $e) {
  3150.             return true;
  3151.         }
  3152.         return $column->isEmpty($definitionOfEmptyFlags);
  3153.     }
  3154.     /**
  3155.      * Implement PHP __clone to create a deep clone, not just a shallow copy.
  3156.      */
  3157.     public function __clone()
  3158.     {
  3159.         // @phpstan-ignore-next-line
  3160.         foreach ($this as $key => $val) {
  3161.             if ($key == 'parent') {
  3162.                 continue;
  3163.             }
  3164.             if (is_object($val) || (is_array($val))) {
  3165.                 if ($key == 'cellCollection') {
  3166.                     $newCollection $this->cellCollection->cloneCellCollection($this);
  3167.                     $this->cellCollection $newCollection;
  3168.                 } elseif ($key == 'drawingCollection') {
  3169.                     $currentCollection $this->drawingCollection;
  3170.                     $this->drawingCollection = new ArrayObject();
  3171.                     foreach ($currentCollection as $item) {
  3172.                         if (is_object($item)) {
  3173.                             $newDrawing = clone $item;
  3174.                             $newDrawing->setWorksheet($this);
  3175.                         }
  3176.                     }
  3177.                 } elseif (($key == 'autoFilter') && ($this->autoFilter instanceof AutoFilter)) {
  3178.                     $newAutoFilter = clone $this->autoFilter;
  3179.                     $this->autoFilter $newAutoFilter;
  3180.                     $this->autoFilter->setParent($this);
  3181.                 } else {
  3182.                     $this->{$key} = unserialize(serialize($val));
  3183.                 }
  3184.             }
  3185.         }
  3186.     }
  3187.     /**
  3188.      * Define the code name of the sheet.
  3189.      *
  3190.      * @param string $codeName Same rule as Title minus space not allowed (but, like Excel, change
  3191.      *                       silently space to underscore)
  3192.      * @param bool $validate False to skip validation of new title. WARNING: This should only be set
  3193.      *                       at parse time (by Readers), where titles can be assumed to be valid.
  3194.      *
  3195.      * @return $this
  3196.      */
  3197.     public function setCodeName($codeName$validate true)
  3198.     {
  3199.         // Is this a 'rename' or not?
  3200.         if ($this->getCodeName() == $codeName) {
  3201.             return $this;
  3202.         }
  3203.         if ($validate) {
  3204.             $codeName str_replace(' ''_'$codeName); //Excel does this automatically without flinching, we are doing the same
  3205.             // Syntax check
  3206.             // throw an exception if not valid
  3207.             self::checkSheetCodeName($codeName);
  3208.             // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
  3209.             if ($this->parent !== null) {
  3210.                 // Is there already such sheet name?
  3211.                 if ($this->parent->sheetCodeNameExists($codeName)) {
  3212.                     // Use name, but append with lowest possible integer
  3213.                     if (Shared\StringHelper::countCharacters($codeName) > 29) {
  3214.                         $codeName Shared\StringHelper::substring($codeName029);
  3215.                     }
  3216.                     $i 1;
  3217.                     while ($this->getParentOrThrow()->sheetCodeNameExists($codeName '_' $i)) {
  3218.                         ++$i;
  3219.                         if ($i == 10) {
  3220.                             if (Shared\StringHelper::countCharacters($codeName) > 28) {
  3221.                                 $codeName Shared\StringHelper::substring($codeName028);
  3222.                             }
  3223.                         } elseif ($i == 100) {
  3224.                             if (Shared\StringHelper::countCharacters($codeName) > 27) {
  3225.                                 $codeName Shared\StringHelper::substring($codeName027);
  3226.                             }
  3227.                         }
  3228.                     }
  3229.                     $codeName .= '_' $i// ok, we have a valid name
  3230.                 }
  3231.             }
  3232.         }
  3233.         $this->codeName $codeName;
  3234.         return $this;
  3235.     }
  3236.     /**
  3237.      * Return the code name of the sheet.
  3238.      *
  3239.      * @return null|string
  3240.      */
  3241.     public function getCodeName()
  3242.     {
  3243.         return $this->codeName;
  3244.     }
  3245.     /**
  3246.      * Sheet has a code name ?
  3247.      *
  3248.      * @return bool
  3249.      */
  3250.     public function hasCodeName()
  3251.     {
  3252.         return $this->codeName !== null;
  3253.     }
  3254.     public static function nameRequiresQuotes(string $sheetName): bool
  3255.     {
  3256.         return preg_match(self::SHEET_NAME_REQUIRES_NO_QUOTES$sheetName) !== 1;
  3257.     }
  3258. }