Skip to content

Commit

Permalink
Split Screens (PHPOffice#3622)
Browse files Browse the repository at this point in the history
* Split Screens

Fix PHPOffice#3601. Split screens are a feature that affects the display of the spreadsheet to the end user; they do not affect the data. They are conceptually similar to "freeze panes". The differences are explained in the issue. As will be explained, support is fairly full for Xlsx, and less full for Xml; no attempt is yet made to support Xls or Ods.

For freeze or split, the window can be divided into 2 horizontal panes, or 2 vertical panes, or 4 horizontal+vertical panes. In Excel, you can split or freeze on cell A1, which causes 4 panes centered at the middle of the screen. PhpSpreadsheet will not duplicate that functionality, and code is added to ignore an attempt to freeze at A1. This breaks one existing nonsensical test, which is changed to something sensible for this PR.

In the spreadsheet xml, both 'freeze' and 'split' use attributes 'xSplit' and 'ySplit' to indicate the position. Unfortunately, the attributes have different meanings for 'freeze' (*columns* from top/left) than for 'split' (*distance* from top/left). For that reason, it is difficult to change between 'freeze' and 'split', so PhpSpreadsheet will not yet support doing so.

There are 3 possible states when freeze/split is used - 'frozen', 'split', or 'frozenSplit'. All will be maintained during read/write, and the user can set them. In Excel, you get 'frozenSplit' by first splitting, then by changing to freezing. In that case, when you unfreeze, you revert to split. PhpSpreadsheet will not duplicate that functionality - when you unfreeze, the panes will go away regardless of whether you were 'frozen' or 'frozenSplit'.

Changing the selected cell will cause the 'active pane' to update when the panes are frozen. PhpSpreadsheet will not yet update the active pane when the panes are split. The programmer will, of course, have the opportunity to explicitly change the active pane in this circumstance.

I have not yet been able to figure out how Xml spreadsheets map their panes, or how to determine selected cell for the sheet as a whole or for individual panes when freeze/split is in effect. The programmer will have the opportunity to specify these explicitly. However, loading an Xml spreadsheet in PhpSpreadsheet will not fill in these values.

Each pane has its own selected cells, and you can navigate between these with the F6 key in Excel (this may work only with split but not with freeze, but Excel maintains the values for freeze and so will PhpSpreadsheet). For Xlsx, PhpSpreadsheet loads and saves these values.

* Eliminate Some Dead Code

Pointed out by Scrutinizer.
  • Loading branch information
oleibman authored Jun 28, 2023
1 parent 263a4a4 commit 616b9de
Show file tree
Hide file tree
Showing 12 changed files with 949 additions and 49 deletions.
54 changes: 43 additions & 11 deletions src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\Pane;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;

Expand All @@ -18,6 +19,8 @@ class SheetViews extends BaseParserClass
/** @var Worksheet */
private $worksheet;

private string $activePane = '';

public function __construct(SimpleXMLElement $sheetViewXml, Worksheet $workSheet)
{
$this->sheetViewXml = $sheetViewXml;
Expand All @@ -35,11 +38,15 @@ public function load(): void
$this->direction();
$this->showZeros();

$usesPanes = false;
if (isset($this->sheetViewXml->pane)) {
$this->pane();
$usesPanes = true;
}
if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection->attributes()->sqref)) {
$this->selection();
if (isset($this->sheetViewXml->selection)) {
foreach ($this->sheetViewXml->selection as $selection) {
$this->selection($selection, $usesPanes);
}
}
}

Expand Down Expand Up @@ -127,30 +134,55 @@ private function pane(): void

if (isset($paneAttributes->xSplit)) {
$xSplit = (int) ($paneAttributes->xSplit);
$this->worksheet->setXSplit($xSplit);
}

if (isset($paneAttributes->ySplit)) {
$ySplit = (int) ($paneAttributes->ySplit);
$this->worksheet->setYSplit($ySplit);
}

$paneState = isset($paneAttributes->state) ? ((string) $paneAttributes->state) : '';
$this->worksheet->setPaneState($paneState);
if (isset($paneAttributes->topLeftCell)) {
$topLeftCell = (string) $paneAttributes->topLeftCell;
$this->worksheet->setPaneTopLeftCell($topLeftCell);
if ($paneState === Worksheet::PANE_FROZEN) {
$this->worksheet->setTopLeftCell($topLeftCell);
}
}
$activePane = isset($paneAttributes->activePane) ? ((string) $paneAttributes->activePane) : 'topLeft';
$this->worksheet->setActivePane($activePane);
$this->activePane = $activePane;
if ($paneState === Worksheet::PANE_FROZEN || $paneState === Worksheet::PANE_FROZENSPLIT) {
$this->worksheet->freezePane(
Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1),
$topLeftCell,
$paneState === Worksheet::PANE_FROZENSPLIT
);
}

$this->worksheet->freezePane(
Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1),
$topLeftCell
);
}

private function selection(): void
private function selection(?SimpleXMLElement $selection, bool $usesPanes): void
{
$attributes = $this->sheetViewXml->selection->attributes();
$attributes = ($selection === null) ? null : $selection->attributes();
if ($attributes !== null) {
$position = (string) $attributes->pane;
if ($usesPanes && $position === '') {
$position = 'topLeft';
}
$activeCell = (string) $attributes->activeCell;
$sqref = (string) $attributes->sqref;
$sqref = explode(' ', $sqref);
$sqref = $sqref[0];
$this->worksheet->setSelectedCells($sqref);
if ($position === '') {
$this->worksheet->setSelectedCells($sqref);
} else {
$pane = new Pane($position, $sqref, $activeCell);
$this->worksheet->setPane($position, $pane);
if ($position === $this->activePane && $sqref !== '') {
$this->worksheet->setSelectedCells($sqref);
}
}
}
}
}
39 changes: 38 additions & 1 deletion src/PhpSpreadsheet/Reader/Xml.php
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,44 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
if (isset($xmlX->WorksheetOptions->SplitVertical)) {
$freezeColumn = (int) $xmlX->WorksheetOptions->SplitVertical + 1;
}
$spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow);
$leftTopRow = (string) $xmlX->WorksheetOptions->TopRowBottomPane;
$leftTopColumn = (string) $xmlX->WorksheetOptions->LeftColumnRightPane;
if (is_numeric($leftTopRow) && is_numeric($leftTopColumn)) {
$leftTopCoordinate = Coordinate::stringFromColumnIndex((int) $leftTopColumn + 1) . (string) ($leftTopRow + 1);
$spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow, $leftTopCoordinate, !isset($xmlX->WorksheetOptions->FrozenNoSplit));
} else {
$spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow, null, !isset($xmlX->WorksheetOptions->FrozenNoSplit));
}
} elseif (isset($xmlX->WorksheetOptions->SplitVertical) || isset($xmlX->WorksheetOptions->SplitHorizontal)) {
if (isset($xmlX->WorksheetOptions->SplitHorizontal)) {
$ySplit = (int) $xmlX->WorksheetOptions->SplitHorizontal;
$spreadsheet->getActiveSheet()->setYSplit($ySplit);
}
if (isset($xmlX->WorksheetOptions->SplitVertical)) {
$xSplit = (int) $xmlX->WorksheetOptions->SplitVertical;
$spreadsheet->getActiveSheet()->setXSplit($xSplit);
}
if (isset($xmlX->WorksheetOptions->LeftColumnVisible) || isset($xmlX->WorksheetOptions->TopRowVisible)) {
$leftTopColumn = $leftTopRow = 1;
if (isset($xmlX->WorksheetOptions->LeftColumnVisible)) {
$leftTopColumn = 1 + (int) $xmlX->WorksheetOptions->LeftColumnVisible;
}
if (isset($xmlX->WorksheetOptions->TopRowVisible)) {
$leftTopRow = 1 + (int) $xmlX->WorksheetOptions->TopRowVisible;
}
$leftTopCoordinate = Coordinate::stringFromColumnIndex($leftTopColumn) . "$leftTopRow";
$spreadsheet->getActiveSheet()->setTopLeftCell($leftTopCoordinate);
}

$leftTopColumn = $leftTopRow = 1;
if (isset($xmlX->WorksheetOptions->LeftColumnRightPane)) {
$leftTopColumn = 1 + (int) $xmlX->WorksheetOptions->LeftColumnRightPane;
}
if (isset($xmlX->WorksheetOptions->TopRowBottomPane)) {
$leftTopRow = 1 + (int) $xmlX->WorksheetOptions->TopRowBottomPane;
}
$leftTopCoordinate = Coordinate::stringFromColumnIndex($leftTopColumn) . "$leftTopRow";
$spreadsheet->getActiveSheet()->setPaneTopLeftCell($leftTopCoordinate);
}
(new PageSettings($xmlX))->loadPageSettings($spreadsheet);
if (isset($xmlX->WorksheetOptions->TopRowVisible, $xmlX->WorksheetOptions->LeftColumnVisible)) {
Expand Down
48 changes: 48 additions & 0 deletions src/PhpSpreadsheet/Worksheet/Pane.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace PhpOffice\PhpSpreadsheet\Worksheet;

class Pane
{
private string $sqref = '';

private string $activeCell = '';

private string $position;

public function __construct(string $position, string $sqref = '', string $activeCell = '')
{
$this->sqref = $sqref;
$this->activeCell = $activeCell;
$this->position = $position;
}

public function getPosition(): string
{
return $this->position;
}

public function getSqref(): string
{
return $this->sqref;
}

public function setSqref(string $sqref): self
{
$this->sqref = $sqref;

return $this;
}

public function getActiveCell(): string
{
return $this->activeCell;
}

public function setActiveCell(string $activeCell): self
{
$this->activeCell = $activeCell;

return $this;
}
}
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Worksheet/Validations.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public static function validateCellRange($cellRange): string
$addressRange = (string) preg_replace(
['/^([A-Z]+):([A-Z]+)$/i', '/^(\\d+):(\\d+)$/'],
[self::SETMAXROW, self::SETMAXCOL],
$addressRange
$addressRange ?? ''
);

return empty($worksheet) ? strtoupper($addressRange) : $worksheet . '!' . strtoupper($addressRange);
Expand Down
Loading

0 comments on commit 616b9de

Please sign in to comment.