diff --git a/docs/topics/accessing-cells.md b/docs/topics/accessing-cells.md index edb71514..a777afc1 100644 --- a/docs/topics/accessing-cells.md +++ b/docs/topics/accessing-cells.md @@ -422,8 +422,10 @@ foreach ($worksheet->getRowIterator() as $row) { $cellIterator = $row->getCellIterator(); $cellIterator->setIterateOnlyExistingCells(FALSE); // This loops through all cells, // even if a cell value is not set. - // By default, only cells that have a value - // set will be iterated. + // For 'TRUE', we loop through cells + // only when their value is set. + // If this method is not called, + // the default value is 'false'. foreach ($cellIterator as $cell) { echo '' . $cell->getValue() . diff --git a/src/PhpSpreadsheet/Style/Style.php b/src/PhpSpreadsheet/Style/Style.php index c1aa319e..f7c1be23 100644 --- a/src/PhpSpreadsheet/Style/Style.php +++ b/src/PhpSpreadsheet/Style/Style.php @@ -42,13 +42,6 @@ class Style extends Supervisor */ protected $numberFormat; - /** - * Conditional styles. - * - * @var Conditional[] - */ - protected $conditionalStyles; - /** * Protection. * @@ -85,7 +78,6 @@ class Style extends Supervisor parent::__construct($isSupervisor); // Initialise values - $this->conditionalStyles = []; $this->font = new Font($isSupervisor, $isConditional); $this->fill = new Fill($isSupervisor, $isConditional); $this->borders = new Borders($isSupervisor, $isConditional); @@ -212,6 +204,8 @@ class Style extends Supervisor $rangeEnd = Coordinate::coordinateFromString($rangeB); // Translate column into index + $rangeStart0 = $rangeStart[0]; + $rangeEnd0 = $rangeEnd[0]; $rangeStart[0] = Coordinate::columnIndexFromString($rangeStart[0]); $rangeEnd[0] = Coordinate::columnIndexFromString($rangeEnd[0]); @@ -361,6 +355,13 @@ class Style extends Supervisor for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true; } + foreach ($this->getActiveSheet()->getColumnIterator($rangeStart0, $rangeEnd0) as $columnIterator) { + $cellIterator = $columnIterator->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(true); + foreach ($cellIterator as $columnCell) { + $columnCell->getStyle()->applyFromArray($pStyles); + } + } break; case 'ROW': @@ -372,6 +373,13 @@ class Style extends Supervisor $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true; } } + foreach ($this->getActiveSheet()->getRowIterator((int) $rangeStart[1], (int) $rangeEnd[1]) as $rowIterator) { + $cellIterator = $rowIterator->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(true); + foreach ($cellIterator as $rowCell) { + $rowCell->getStyle()->applyFromArray($pStyles); + } + } break; case 'CELL': @@ -599,18 +607,12 @@ class Style extends Supervisor */ public function getHashCode() { - $hashConditionals = ''; - foreach ($this->conditionalStyles as $conditional) { - $hashConditionals .= $conditional->getHashCode(); - } - return md5( $this->fill->getHashCode() . $this->font->getHashCode() . $this->borders->getHashCode() . $this->alignment->getHashCode() . $this->numberFormat->getHashCode() . - $hashConditionals . $this->protection->getHashCode() . ($this->quotePrefix ? 't' : 'f') . __CLASS__ diff --git a/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php b/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php index 12420d77..714ee7ce 100644 --- a/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php +++ b/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php @@ -92,10 +92,11 @@ class ColumnCellIterator extends CellIterator */ public function seek($row = 1) { + if ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $row))) { + throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); + } if (($row < $this->startRow) || ($row > $this->endRow)) { throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})"); - } elseif ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $row))) { - throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); } $this->currentRow = $row; @@ -113,7 +114,7 @@ class ColumnCellIterator extends CellIterator /** * Return the current cell in this worksheet column. * - * @return null|\PhpOffice\PhpSpreadsheet\Cell\Cell + * @return \PhpOffice\PhpSpreadsheet\Cell\Cell */ public function current() { @@ -180,18 +181,12 @@ class ColumnCellIterator extends CellIterator ) { ++$this->startRow; } - if ($this->startRow > $this->endRow) { - throw new PhpSpreadsheetException('No cells exist within the specified range'); - } while ( (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->endRow)) && ($this->endRow >= $this->startRow) ) { --$this->endRow; } - if ($this->endRow < $this->startRow) { - throw new PhpSpreadsheetException('No cells exist within the specified range'); - } } } } diff --git a/src/PhpSpreadsheet/Worksheet/RowCellIterator.php b/src/PhpSpreadsheet/Worksheet/RowCellIterator.php index f5576dc7..9b9d54eb 100644 --- a/src/PhpSpreadsheet/Worksheet/RowCellIterator.php +++ b/src/PhpSpreadsheet/Worksheet/RowCellIterator.php @@ -93,12 +93,14 @@ class RowCellIterator extends CellIterator */ public function seek($column = 'A') { + $columnx = $column; $column = Coordinate::columnIndexFromString($column); - if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) { - throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})"); - } elseif ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($column, $this->rowIndex))) { + if ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($column, $this->rowIndex))) { throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); } + if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) { + throw new PhpSpreadsheetException("Column $columnx is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})"); + } $this->currentColumnIndex = $column; return $this; @@ -181,15 +183,9 @@ class RowCellIterator extends CellIterator while ((!$this->worksheet->cellExistsByColumnAndRow($this->startColumnIndex, $this->rowIndex)) && ($this->startColumnIndex <= $this->endColumnIndex)) { ++$this->startColumnIndex; } - if ($this->startColumnIndex > $this->endColumnIndex) { - throw new PhpSpreadsheetException('No cells exist within the specified range'); - } while ((!$this->worksheet->cellExistsByColumnAndRow($this->endColumnIndex, $this->rowIndex)) && ($this->endColumnIndex >= $this->startColumnIndex)) { --$this->endColumnIndex; } - if ($this->endColumnIndex < $this->startColumnIndex) { - throw new PhpSpreadsheetException('No cells exist within the specified range'); - } } } } diff --git a/tests/PhpSpreadsheetTests/Style/StyleTest.php b/tests/PhpSpreadsheetTests/Style/StyleTest.php index b695a50b..6f157709 100644 --- a/tests/PhpSpreadsheetTests/Style/StyleTest.php +++ b/tests/PhpSpreadsheetTests/Style/StyleTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Style; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Fill; use PHPUnit\Framework\TestCase; class StyleTest extends TestCase @@ -19,4 +20,141 @@ class StyleTest extends TestCase $outArray = $cell1style->getStyleArray($styleArray); self::assertEquals($styleArray, $outArray['quotePrefix']); } + + public function testStyleColumn(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $cellCoordinates = 'A:B'; + $styleArray = [ + 'font' => [ + 'bold' => true, + ], + ]; + $sheet->getStyle($cellCoordinates)->applyFromArray($styleArray); + $sheet->setCellValue('A1', 'xxxa1'); + $sheet->setCellValue('A2', 'xxxa2'); + $sheet->setCellValue('A3', 'xxxa3'); + $sheet->setCellValue('B1', 'xxxa1'); + $sheet->setCellValue('B2', 'xxxa2'); + $sheet->setCellValue('B3', 'xxxa3'); + $sheet->setCellValue('C1', 'xxxc1'); + $sheet->setCellValue('C2', 'xxxc2'); + $sheet->setCellValue('C3', 'xxxc3'); + $styleArray = [ + 'font' => [ + 'italic' => true, + ], + ]; + $sheet->getStyle($cellCoordinates)->applyFromArray($styleArray); + self::assertTrue($sheet->getStyle('A1')->getFont()->getBold()); + self::assertTrue($sheet->getStyle('B2')->getFont()->getBold()); + self::assertFalse($sheet->getStyle('C3')->getFont()->getBold()); + self::assertTrue($sheet->getStyle('A1')->getFont()->getItalic()); + self::assertTrue($sheet->getStyle('B2')->getFont()->getItalic()); + self::assertFalse($sheet->getStyle('C3')->getFont()->getItalic()); + } + + public function testStyleRow(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $cellCoordinates = '2:3'; + $styleArray = [ + 'font' => [ + 'bold' => true, + ], + ]; + $sheet->getStyle($cellCoordinates)->applyFromArray($styleArray); + $sheet->setCellValue('A1', 'xxxa1'); + $sheet->setCellValue('A2', 'xxxa2'); + $sheet->setCellValue('A3', 'xxxa3'); + $sheet->setCellValue('B1', 'xxxa1'); + $sheet->setCellValue('B2', 'xxxa2'); + $sheet->setCellValue('B3', 'xxxa3'); + $sheet->setCellValue('C1', 'xxxc1'); + $sheet->setCellValue('C2', 'xxxc2'); + $sheet->setCellValue('C3', 'xxxc3'); + $styleArray = [ + 'font' => [ + 'italic' => true, + ], + ]; + $sheet->getStyle($cellCoordinates)->applyFromArray($styleArray); + self::assertFalse($sheet->getStyle('A1')->getFont()->getBold()); + self::assertTrue($sheet->getStyle('B2')->getFont()->getBold()); + self::assertTrue($sheet->getStyle('C3')->getFont()->getBold()); + self::assertFalse($sheet->getStyle('A1')->getFont()->getItalic()); + self::assertTrue($sheet->getStyle('B2')->getFont()->getItalic()); + self::assertTrue($sheet->getStyle('C3')->getFont()->getItalic()); + } + + public function testIssue1712A(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $rgb = '4467b8'; + $sheet->fromArray(['OK', 'KO']); + $spreadsheet->getActiveSheet() + ->getStyle('A1') + ->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getStartColor() + ->setRGB($rgb); + $spreadsheet->getActiveSheet() + ->getStyle('B') + ->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getStartColor() + ->setRGB($rgb); + self::assertEquals($rgb, $sheet->getCell('A1')->getStyle()->getFill()->getStartColor()->getRGB()); + self::assertEquals($rgb, $sheet->getCell('B1')->getStyle()->getFill()->getStartColor()->getRGB()); + } + + public function testIssue1712B(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $rgb = '4467b8'; + $spreadsheet->getActiveSheet() + ->getStyle('A1') + ->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getStartColor() + ->setRGB($rgb); + $spreadsheet->getActiveSheet() + ->getStyle('B') + ->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getStartColor() + ->setRGB($rgb); + $sheet->fromArray(['OK', 'KO']); + self::assertEquals($rgb, $sheet->getCell('A1')->getStyle()->getFill()->getStartColor()->getRGB()); + self::assertEquals($rgb, $sheet->getCell('B1')->getStyle()->getFill()->getStartColor()->getRGB()); + } + + public function testStyleLoopUpwards(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $cellCoordinates = 'C5:A3'; + $styleArray = [ + 'font' => [ + 'bold' => true, + ], + ]; + $sheet->getStyle($cellCoordinates)->applyFromArray($styleArray); + $sheet->setCellValue('A1', 'xxxa1'); + $sheet->setCellValue('A2', 'xxxa2'); + $sheet->setCellValue('A3', 'xxxa3'); + $sheet->setCellValue('B1', 'xxxa1'); + $sheet->setCellValue('B2', 'xxxa2'); + $sheet->setCellValue('B3', 'xxxa3'); + $sheet->setCellValue('C1', 'xxxc1'); + $sheet->setCellValue('C2', 'xxxc2'); + $sheet->setCellValue('C3', 'xxxc3'); + self::assertFalse($sheet->getStyle('A1')->getFont()->getBold()); + self::assertFalse($sheet->getStyle('B2')->getFont()->getBold()); + self::assertTrue($sheet->getStyle('C3')->getFont()->getBold()); + } } diff --git a/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIterator2Test.php b/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIterator2Test.php new file mode 100644 index 00000000..c542d89e --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIterator2Test.php @@ -0,0 +1,75 @@ +getActiveSheet(); + $sheet->getCell('B2')->setValue('cellb2'); + $sheet->getCell('B6')->setValue('cellb6'); + + $iterator = new ColumnCellIterator($sheet, 'B', 1, 8); + if (isset($existing)) { + $iterator->setIterateOnlyExistingCells($existing); + } + $lastCoordinate = ''; + $firstCoordinate = ''; + foreach ($iterator as $cell) { + $lastCoordinate = $cell->getCoordinate(); + if (!$firstCoordinate) { + $firstCoordinate = $lastCoordinate; + } + } + self::assertEquals($expectedResultFirst, $firstCoordinate); + self::assertEquals($expectedResultLast, $lastCoordinate); + } + + public function providerExistingCell(): array + { + return [ + [null, 'B1', 'B8'], + [false, 'B1', 'B8'], + [true, 'B2', 'B6'], + ]; + } + + /** + * @dataProvider providerEmptyColumn + */ + public function testEmptyColumn(?bool $existing, int $expectedResult): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getCell('B2')->setValue('cellb2'); + $sheet->getCell('B6')->setValue('cellb6'); + + $iterator = new ColumnCellIterator($sheet, 'C'); + if (isset($existing)) { + $iterator->setIterateOnlyExistingCells($existing); + } + $numCells = 0; + foreach ($iterator as $cell) { + ++$numCells; + } + self::assertEquals($expectedResult, $numCells); + } + + public function providerEmptyColumn(): array + { + return [ + [null, 6], + [false, 6], + [true, 0], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php b/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php index 2083f347..1fa25330 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/ColumnCellIteratorTest.php @@ -71,11 +71,21 @@ class ColumnCellIteratorTest extends TestCase public function testSeekOutOfRange(): void { $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); - + $this->expectExceptionMessage('Row 1 is out of range'); $iterator = new ColumnCellIterator($this->mockWorksheet, 'A', 2, 4); $iterator->seek(1); } + public function testSeekNotExisting(): void + { + $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); + $this->expectExceptionMessage('Cell does not exist'); + + $iterator = new ColumnCellIterator($this->mockWorksheet, 'A', 2, 4); + $iterator->setIterateOnlyExistingCells(true); + $iterator->seek(2); + } + public function testPrevOutOfRange(): void { $iterator = new ColumnCellIterator($this->mockWorksheet, 'A', 2, 4); diff --git a/tests/PhpSpreadsheetTests/Worksheet/RowCellIterator2Test.php b/tests/PhpSpreadsheetTests/Worksheet/RowCellIterator2Test.php new file mode 100644 index 00000000..20d10da9 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/RowCellIterator2Test.php @@ -0,0 +1,75 @@ +getActiveSheet(); + $sheet->getCell('C2')->setValue('cellb2'); + $sheet->getCell('F2')->setValue('cellf2'); + + $iterator = new RowCellIterator($sheet, 2, 'B', 'H'); + if (isset($existing)) { + $iterator->setIterateOnlyExistingCells($existing); + } + $lastCoordinate = ''; + $firstCoordinate = ''; + foreach ($iterator as $cell) { + $lastCoordinate = $cell->getCoordinate(); + if (!$firstCoordinate) { + $firstCoordinate = $lastCoordinate; + } + } + self::assertEquals($expectedResultFirst, $firstCoordinate); + self::assertEquals($expectedResultLast, $lastCoordinate); + } + + public function providerExistingCell(): array + { + return [ + [null, 'B2', 'H2'], + [false, 'B2', 'H2'], + [true, 'C2', 'F2'], + ]; + } + + /** + * @dataProvider providerEmptyRow + */ + public function testEmptyRow(?bool $existing, int $expectedResult): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getCell('B2')->setValue('cellb2'); + $sheet->getCell('F2')->setValue('cellf2'); + + $iterator = new RowCellIterator($sheet, '3'); + if (isset($existing)) { + $iterator->setIterateOnlyExistingCells($existing); + } + $numCells = 0; + foreach ($iterator as $cell) { + ++$numCells; + } + self::assertEquals($expectedResult, $numCells); + } + + public function providerEmptyRow(): array + { + return [ + [null, 6], + [false, 6], + [true, 0], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php b/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php index bc2c16dc..4105c91c 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/RowCellIteratorTest.php @@ -73,9 +73,22 @@ class RowCellIteratorTest extends TestCase public function testSeekOutOfRange(): void { $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); + $this->expectExceptionMessage('Column A is out of range'); $iterator = new RowCellIterator($this->mockWorksheet, 2, 'B', 'D'); - $iterator->seek(1); + self::assertFalse($iterator->getIterateOnlyExistingCells()); + self::assertEquals(2, $iterator->getCurrentColumnIndex()); + $iterator->seek('A'); + } + + public function testSeekNotExisting(): void + { + $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); + $this->expectExceptionMessage('Cell does not exist'); + + $iterator = new RowCellIterator($this->mockWorksheet, 2, 'B', 'D'); + $iterator->setIterateOnlyExistingCells(true); + $iterator->seek('B'); } public function testPrevOutOfRange(): void