Skip to content

Commit 616b9de

Browse files
authored
Split Screens (#3622)
* Split Screens Fix #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.
1 parent 263a4a4 commit 616b9de

File tree

12 files changed

+949
-49
lines changed

12 files changed

+949
-49
lines changed

src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php

+43-11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
66
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
7+
use PhpOffice\PhpSpreadsheet\Worksheet\Pane;
78
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
89
use SimpleXMLElement;
910

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

22+
private string $activePane = '';
23+
2124
public function __construct(SimpleXMLElement $sheetViewXml, Worksheet $workSheet)
2225
{
2326
$this->sheetViewXml = $sheetViewXml;
@@ -35,11 +38,15 @@ public function load(): void
3538
$this->direction();
3639
$this->showZeros();
3740

41+
$usesPanes = false;
3842
if (isset($this->sheetViewXml->pane)) {
3943
$this->pane();
44+
$usesPanes = true;
4045
}
41-
if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection->attributes()->sqref)) {
42-
$this->selection();
46+
if (isset($this->sheetViewXml->selection)) {
47+
foreach ($this->sheetViewXml->selection as $selection) {
48+
$this->selection($selection, $usesPanes);
49+
}
4350
}
4451
}
4552

@@ -127,30 +134,55 @@ private function pane(): void
127134

128135
if (isset($paneAttributes->xSplit)) {
129136
$xSplit = (int) ($paneAttributes->xSplit);
137+
$this->worksheet->setXSplit($xSplit);
130138
}
131139

132140
if (isset($paneAttributes->ySplit)) {
133141
$ySplit = (int) ($paneAttributes->ySplit);
142+
$this->worksheet->setYSplit($ySplit);
134143
}
135-
144+
$paneState = isset($paneAttributes->state) ? ((string) $paneAttributes->state) : '';
145+
$this->worksheet->setPaneState($paneState);
136146
if (isset($paneAttributes->topLeftCell)) {
137147
$topLeftCell = (string) $paneAttributes->topLeftCell;
148+
$this->worksheet->setPaneTopLeftCell($topLeftCell);
149+
if ($paneState === Worksheet::PANE_FROZEN) {
150+
$this->worksheet->setTopLeftCell($topLeftCell);
151+
}
152+
}
153+
$activePane = isset($paneAttributes->activePane) ? ((string) $paneAttributes->activePane) : 'topLeft';
154+
$this->worksheet->setActivePane($activePane);
155+
$this->activePane = $activePane;
156+
if ($paneState === Worksheet::PANE_FROZEN || $paneState === Worksheet::PANE_FROZENSPLIT) {
157+
$this->worksheet->freezePane(
158+
Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1),
159+
$topLeftCell,
160+
$paneState === Worksheet::PANE_FROZENSPLIT
161+
);
138162
}
139-
140-
$this->worksheet->freezePane(
141-
Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1),
142-
$topLeftCell
143-
);
144163
}
145164

146-
private function selection(): void
165+
private function selection(?SimpleXMLElement $selection, bool $usesPanes): void
147166
{
148-
$attributes = $this->sheetViewXml->selection->attributes();
167+
$attributes = ($selection === null) ? null : $selection->attributes();
149168
if ($attributes !== null) {
169+
$position = (string) $attributes->pane;
170+
if ($usesPanes && $position === '') {
171+
$position = 'topLeft';
172+
}
173+
$activeCell = (string) $attributes->activeCell;
150174
$sqref = (string) $attributes->sqref;
151175
$sqref = explode(' ', $sqref);
152176
$sqref = $sqref[0];
153-
$this->worksheet->setSelectedCells($sqref);
177+
if ($position === '') {
178+
$this->worksheet->setSelectedCells($sqref);
179+
} else {
180+
$pane = new Pane($position, $sqref, $activeCell);
181+
$this->worksheet->setPane($position, $pane);
182+
if ($position === $this->activePane && $sqref !== '') {
183+
$this->worksheet->setSelectedCells($sqref);
184+
}
185+
}
154186
}
155187
}
156188
}

src/PhpSpreadsheet/Reader/Xml.php

+38-1
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,44 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
525525
if (isset($xmlX->WorksheetOptions->SplitVertical)) {
526526
$freezeColumn = (int) $xmlX->WorksheetOptions->SplitVertical + 1;
527527
}
528-
$spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow);
528+
$leftTopRow = (string) $xmlX->WorksheetOptions->TopRowBottomPane;
529+
$leftTopColumn = (string) $xmlX->WorksheetOptions->LeftColumnRightPane;
530+
if (is_numeric($leftTopRow) && is_numeric($leftTopColumn)) {
531+
$leftTopCoordinate = Coordinate::stringFromColumnIndex((int) $leftTopColumn + 1) . (string) ($leftTopRow + 1);
532+
$spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow, $leftTopCoordinate, !isset($xmlX->WorksheetOptions->FrozenNoSplit));
533+
} else {
534+
$spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow, null, !isset($xmlX->WorksheetOptions->FrozenNoSplit));
535+
}
536+
} elseif (isset($xmlX->WorksheetOptions->SplitVertical) || isset($xmlX->WorksheetOptions->SplitHorizontal)) {
537+
if (isset($xmlX->WorksheetOptions->SplitHorizontal)) {
538+
$ySplit = (int) $xmlX->WorksheetOptions->SplitHorizontal;
539+
$spreadsheet->getActiveSheet()->setYSplit($ySplit);
540+
}
541+
if (isset($xmlX->WorksheetOptions->SplitVertical)) {
542+
$xSplit = (int) $xmlX->WorksheetOptions->SplitVertical;
543+
$spreadsheet->getActiveSheet()->setXSplit($xSplit);
544+
}
545+
if (isset($xmlX->WorksheetOptions->LeftColumnVisible) || isset($xmlX->WorksheetOptions->TopRowVisible)) {
546+
$leftTopColumn = $leftTopRow = 1;
547+
if (isset($xmlX->WorksheetOptions->LeftColumnVisible)) {
548+
$leftTopColumn = 1 + (int) $xmlX->WorksheetOptions->LeftColumnVisible;
549+
}
550+
if (isset($xmlX->WorksheetOptions->TopRowVisible)) {
551+
$leftTopRow = 1 + (int) $xmlX->WorksheetOptions->TopRowVisible;
552+
}
553+
$leftTopCoordinate = Coordinate::stringFromColumnIndex($leftTopColumn) . "$leftTopRow";
554+
$spreadsheet->getActiveSheet()->setTopLeftCell($leftTopCoordinate);
555+
}
556+
557+
$leftTopColumn = $leftTopRow = 1;
558+
if (isset($xmlX->WorksheetOptions->LeftColumnRightPane)) {
559+
$leftTopColumn = 1 + (int) $xmlX->WorksheetOptions->LeftColumnRightPane;
560+
}
561+
if (isset($xmlX->WorksheetOptions->TopRowBottomPane)) {
562+
$leftTopRow = 1 + (int) $xmlX->WorksheetOptions->TopRowBottomPane;
563+
}
564+
$leftTopCoordinate = Coordinate::stringFromColumnIndex($leftTopColumn) . "$leftTopRow";
565+
$spreadsheet->getActiveSheet()->setPaneTopLeftCell($leftTopCoordinate);
529566
}
530567
(new PageSettings($xmlX))->loadPageSettings($spreadsheet);
531568
if (isset($xmlX->WorksheetOptions->TopRowVisible, $xmlX->WorksheetOptions->LeftColumnVisible)) {

src/PhpSpreadsheet/Worksheet/Pane.php

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Worksheet;
4+
5+
class Pane
6+
{
7+
private string $sqref = '';
8+
9+
private string $activeCell = '';
10+
11+
private string $position;
12+
13+
public function __construct(string $position, string $sqref = '', string $activeCell = '')
14+
{
15+
$this->sqref = $sqref;
16+
$this->activeCell = $activeCell;
17+
$this->position = $position;
18+
}
19+
20+
public function getPosition(): string
21+
{
22+
return $this->position;
23+
}
24+
25+
public function getSqref(): string
26+
{
27+
return $this->sqref;
28+
}
29+
30+
public function setSqref(string $sqref): self
31+
{
32+
$this->sqref = $sqref;
33+
34+
return $this;
35+
}
36+
37+
public function getActiveCell(): string
38+
{
39+
return $this->activeCell;
40+
}
41+
42+
public function setActiveCell(string $activeCell): self
43+
{
44+
$this->activeCell = $activeCell;
45+
46+
return $this;
47+
}
48+
}

src/PhpSpreadsheet/Worksheet/Validations.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public static function validateCellRange($cellRange): string
7373
$addressRange = (string) preg_replace(
7474
['/^([A-Z]+):([A-Z]+)$/i', '/^(\\d+):(\\d+)$/'],
7575
[self::SETMAXROW, self::SETMAXCOL],
76-
$addressRange
76+
$addressRange ?? ''
7777
);
7878

7979
return empty($worksheet) ? strtoupper($addressRange) : $worksheet . '!' . strtoupper($addressRange);

0 commit comments

Comments
 (0)