diff --git a/CHANGELOG.md b/CHANGELOG.md index 12732fa3..ff62b1f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Support to write merged cells in ODS format [#287](https://github.com/PHPOffice/PhpSpreadsheet/issues/287) +- Able to set the `topLeftCell` in freeze panes [#261](https://github.com/PHPOffice/PhpSpreadsheet/pull/261) ### Changed diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index a8cfb8e2..02d99f01 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -4488,9 +4488,17 @@ class Xls extends BaseReader // offset: 2; size: 2; position of horizontal split $py = self::getUInt2d($recordData, 2); + // offset: 4; size: 2; top most visible row in the bottom pane + $rwTop = self::getUInt2d($recordData, 4); + + // offset: 6; size: 2; first visible left column in the right pane + $colLeft = self::getUInt2d($recordData, 6); + if ($this->frozen) { // frozen panes - $this->phpSheet->freezePane(Coordinate::stringFromColumnIndex($px + 1) . ($py + 1)); + $cell = Coordinate::stringFromColumnIndex($px + 1) . ($py + 1); + $topLeftCell = Coordinate::stringFromColumnIndex($colLeft + 1) . ($rwTop + 1); + $this->phpSheet->freezePane($cell, $topLeftCell); } // unfrozen panes; split windows; not supported by PhpSpreadsheet core } diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 1c249cae..00ebb9a9 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -720,22 +720,23 @@ class Xlsx extends BaseReader $docSheet->setRightToLeft(self::boolean((string) $xmlSheet->sheetViews->sheetView['rightToLeft'])); } if (isset($xmlSheet->sheetViews->sheetView->pane)) { - if (isset($xmlSheet->sheetViews->sheetView->pane['topLeftCell'])) { - $docSheet->freezePane((string) $xmlSheet->sheetViews->sheetView->pane['topLeftCell']); - } else { - $xSplit = 0; - $ySplit = 0; + $xSplit = 0; + $ySplit = 0; + $topLeftCell = null; - if (isset($xmlSheet->sheetViews->sheetView->pane['xSplit'])) { - $xSplit = 1 + (int) ($xmlSheet->sheetViews->sheetView->pane['xSplit']); - } - - if (isset($xmlSheet->sheetViews->sheetView->pane['ySplit'])) { - $ySplit = 1 + (int) ($xmlSheet->sheetViews->sheetView->pane['ySplit']); - } - - $docSheet->freezePaneByColumnAndRow($xSplit + 1, $ySplit); + if (isset($xmlSheet->sheetViews->sheetView->pane['xSplit'])) { + $xSplit = (int) ($xmlSheet->sheetViews->sheetView->pane['xSplit']); } + + if (isset($xmlSheet->sheetViews->sheetView->pane['ySplit'])) { + $ySplit = (int) ($xmlSheet->sheetViews->sheetView->pane['ySplit']); + } + + if (isset($xmlSheet->sheetViews->sheetView->pane['topLeftCell'])) { + $topLeftCell = (string) $xmlSheet->sheetViews->sheetView->pane['topLeftCell']; + } + + $docSheet->freezePane(Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1), $topLeftCell); } if (isset($xmlSheet->sheetViews->sheetView->selection)) { diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index bfa14108..3ccdba0c 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -576,8 +576,14 @@ class ReferenceHelper } // Update worksheet: freeze pane - if ($pSheet->getFreezePane() != '') { - $pSheet->freezePane($this->updateCellReference($pSheet->getFreezePane(), $pBefore, $pNumCols, $pNumRows)); + if ($pSheet->getFreezePane()) { + $splitCell = $pSheet->getFreezePane(); + $topLeftCell = $pSheet->getTopLeftCell(); + + $splitCell = $this->updateCellReference($splitCell, $pBefore, $pNumCols, $pNumRows); + $topLeftCell = $this->updateCellReference($topLeftCell, $pBefore, $pNumCols, $pNumRows); + + $pSheet->freezePane($splitCell, $topLeftCell); } // Page setup diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 04277a7f..ee270615 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -201,9 +201,16 @@ class Worksheet implements IComparable /** * Freeze pane. * - * @var string + * @var null|string */ - private $freezePane = ''; + private $freezePane; + + /** + * Default position of the right bottom pane. + * + * @var null|string + */ + private $topLeftCell; /** * Show gridlines? @@ -1975,27 +1982,33 @@ class Worksheet implements IComparable /** * Freeze Pane. * - * @param string $pCell Cell (i.e. A2) - * Examples: - * A2 will freeze the rows above cell A2 (i.e row 1) - * B1 will freeze the columns to the left of cell B1 (i.e column A) - * B2 will freeze the rows above and to the left of cell A2 - * (i.e row 1 and column A) + * Examples: + * + * - A2 will freeze the rows above cell A2 (i.e row 1) + * - B1 will freeze the columns to the left of cell B1 (i.e column A) + * - B2 will freeze the rows above and to the left of cell A2 (i.e row 1 and column A) + * + * @param null|string $cell Position of the split + * @param null|string $topLeftCell default position of the right bottom pane * * @throws Exception * * @return Worksheet */ - public function freezePane($pCell) + public function freezePane($cell, $topLeftCell = null) { - // Uppercase coordinate - $pCell = strtoupper($pCell); - if (strpos($pCell, ':') === false && strpos($pCell, ',') === false) { - $this->freezePane = $pCell; - } else { + if (is_string($cell) && (strpos($cell, ':') !== false || strpos($cell, ',') !== false)) { throw new Exception('Freeze pane can not be set on a range of cells.'); } + if ($cell !== null && $topLeftCell === null) { + $coordinate = Coordinate::coordinateFromString($cell); + $topLeftCell = $coordinate[0] . ($coordinate[1] + 1); + } + + $this->freezePane = $cell; + $this->topLeftCell = $topLeftCell; + return $this; } @@ -2005,8 +2018,6 @@ class Worksheet implements IComparable * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * - * @throws Exception - * * @return Worksheet */ public function freezePaneByColumnAndRow($columnIndex, $row) @@ -2021,7 +2032,17 @@ class Worksheet implements IComparable */ public function unfreezePane() { - return $this->freezePane(''); + return $this->freezePane(null); + } + + /** + * Get the default position of the right bottom pane. + * + * @return int + */ + public function getTopLeftCell() + { + return $this->topLeftCell; } /** @@ -2622,6 +2643,7 @@ class Worksheet implements IComparable // Identify the range that we need to extract from the worksheet $maxCol = $this->getHighestColumn(); $maxRow = $this->getHighestRow(); + // Return return $this->rangeToArray('A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef); } diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index 5f374781..2257e075 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -1589,10 +1589,15 @@ class Worksheet extends BIFFwriter private function writePanes() { $panes = []; - if ($freezePane = $this->phpSheet->getFreezePane()) { - list($column, $row) = Coordinate::coordinateFromString($freezePane); - $panes[0] = $row - 1; - $panes[1] = Coordinate::columnIndexFromString($column) - 1; + if ($this->phpSheet->getFreezePane()) { + list($column, $row) = Coordinate::coordinateFromString($this->phpSheet->getFreezePane()); + $panes[0] = Coordinate::columnIndexFromString($column) - 1; + $panes[1] = $row - 1; + + list($leftMostColumn, $topRow) = Coordinate::coordinateFromString($this->phpSheet->getTopLeftCell()); + //Coordinates are zero-based in xls files + $panes[2] = $topRow - 1; + $panes[3] = Coordinate::columnIndexFromString($leftMostColumn) - 1; } else { // thaw panes return; diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 17a80205..8dc6af38 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -244,31 +244,31 @@ class Worksheet extends WriterPart // Pane $pane = ''; - $topLeftCell = $pSheet->getFreezePane(); - if (($topLeftCell != '') && ($topLeftCell != 'A1')) { - $activeCell = $topLeftCell; - // Calculate freeze coordinates - $xSplit = $ySplit = 0; - - list($xSplit, $ySplit) = Coordinate::coordinateFromString($topLeftCell); + if ($pSheet->getFreezePane()) { + list($xSplit, $ySplit) = Coordinate::coordinateFromString($pSheet->getFreezePane()); $xSplit = Coordinate::columnIndexFromString($xSplit); + --$xSplit; + --$ySplit; + + $topLeftCell = $pSheet->getTopLeftCell(); + $activeCell = $topLeftCell; // pane $pane = 'topRight'; $objWriter->startElement('pane'); - if ($xSplit > 1) { - $objWriter->writeAttribute('xSplit', $xSplit - 1); + if ($xSplit > 0) { + $objWriter->writeAttribute('xSplit', $xSplit); } - if ($ySplit > 1) { - $objWriter->writeAttribute('ySplit', $ySplit - 1); - $pane = ($xSplit > 1) ? 'bottomRight' : 'bottomLeft'; + if ($ySplit > 0) { + $objWriter->writeAttribute('ySplit', $ySplit); + $pane = ($xSplit > 0) ? 'bottomRight' : 'bottomLeft'; } $objWriter->writeAttribute('topLeftCell', $topLeftCell); $objWriter->writeAttribute('activePane', $pane); $objWriter->writeAttribute('state', 'frozen'); $objWriter->endElement(); - if (($xSplit > 1) && ($ySplit > 1)) { + if (($xSplit > 0) && ($ySplit > 0)) { // Write additional selections if more than two panes (ie both an X and a Y split) $objWriter->startElement('selection'); $objWriter->writeAttribute('pane', 'topRight'); diff --git a/tests/PhpSpreadsheetTests/Reader/XlsTest.php b/tests/PhpSpreadsheetTests/Reader/XlsTest.php new file mode 100644 index 00000000..be435e83 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/XlsTest.php @@ -0,0 +1,37 @@ +getActiveSheet(); + $active->freezePane($cellSplit, $topLeftCell); + + $writer = new WriterXls($spreadsheet); + $writer->save($filename); + + // Read written file + $reader = new ReaderXls(); + $reloadedSpreadsheet = $reader->load($filename); + $reloadedActive = $reloadedSpreadsheet->getActiveSheet(); + $actualCellSplit = $reloadedActive->getFreezePane(); + $actualTopLeftCell = $reloadedActive->getTopLeftCell(); + + self::assertSame($cellSplit, $actualCellSplit, 'should be able to set freeze pane'); + self::assertSame($topLeftCell, $actualTopLeftCell, 'should be able to set the top left cell'); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php index fd14714d..a379fdac 100644 --- a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php @@ -2,7 +2,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader; -use PhpOffice\PhpSpreadsheet\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx as ReaderXlsx; +use PhpOffice\PhpSpreadsheet\Shared\File; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx as WriterXlsx; use PHPUnit\Framework\TestCase; class XlsxTest extends TestCase @@ -13,7 +16,32 @@ class XlsxTest extends TestCase public function testLoadXlsxWithoutCellReference() { $filename = './data/Reader/XLSX/without_cell_reference.xlsx'; - $reader = new Xlsx(); + $reader = new ReaderXlsx(); $reader->load($filename); } + + public function testFreezePane() + { + $filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet'); + + $cellSplit = 'B2'; + $topLeftCell = 'E5'; + + $spreadsheet = new Spreadsheet(); + $active = $spreadsheet->getActiveSheet(); + $active->freezePane($cellSplit, $topLeftCell); + + $writer = new WriterXlsx($spreadsheet); + $writer->save($filename); + + // Read written file + $reader = new ReaderXlsx(); + $reloadedSpreadsheet = $reader->load($filename); + $reloadedActive = $reloadedSpreadsheet->getActiveSheet(); + $actualCellSplit = $reloadedActive->getFreezePane(); + $actualTopLeftCell = $reloadedActive->getTopLeftCell(); + + self::assertSame($cellSplit, $actualCellSplit, 'should be able to set freeze pane'); + self::assertSame($topLeftCell, $actualTopLeftCell, 'should be able to set the top left cell'); + } }