feat: add sheet view support to XLSX writer
This makes it possible to freeze rows and columns, in addition to providing other sheet view settings like zoom options.
This commit is contained in:
parent
ab973cab34
commit
b54e217799
@ -50,6 +50,23 @@ $writer->setShouldCreateNewSheetsAutomatically(true); // default value
|
||||
$writer->setShouldCreateNewSheetsAutomatically(false); // will stop writing new data when limit is reached
|
||||
```
|
||||
|
||||
### Sheet view (XLSX writer)
|
||||
|
||||
Sheet view settings must be configured before any rows are added to the sheet.
|
||||
|
||||
```php
|
||||
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
|
||||
use Box\Spout\Writer\XLSX\Entity\SheetView;
|
||||
|
||||
$sheetView = (new SheetView())
|
||||
->setFreezeRow(2) // First row will be fixed
|
||||
->setFreezeColumn('D') // Columns A to C will be fixed
|
||||
->setZoomScale(150); // And other options
|
||||
|
||||
$writer = WriterEntityFactory::createXLSXWriter();
|
||||
$writer->getCurrentSheet()->setSheetView($sheetView);
|
||||
```
|
||||
|
||||
### Using a custom temporary folder
|
||||
|
||||
Processing XLSX and ODS files requires temporary files to be created. By default, {{ site.spout_html }} will use the system default temporary folder (as returned by `sys_get_temp_dir()`). It is possible to override this by explicitly setting it on the reader or writer:
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Box\Spout\Writer\Common\Entity;
|
||||
|
||||
use Box\Spout\Writer\Common\Manager\SheetManager;
|
||||
use Box\Spout\Writer\XLSX\Entity\SheetView;
|
||||
|
||||
/**
|
||||
* Class Sheet
|
||||
@ -27,6 +28,9 @@ class Sheet
|
||||
/** @var SheetManager Sheet manager */
|
||||
private $sheetManager;
|
||||
|
||||
/** @var SheetView */
|
||||
private $sheetView;
|
||||
|
||||
/**
|
||||
* @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
|
||||
* @param string $associatedWorkbookId ID of the sheet's associated workbook
|
||||
@ -108,4 +112,31 @@ class Sheet
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SheetView|null
|
||||
*/
|
||||
public function getSheetView(): ?SheetView
|
||||
{
|
||||
return $this->sheetView;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SheetView $sheetView
|
||||
* @return $this
|
||||
*/
|
||||
public function setSheetView(SheetView $sheetView)
|
||||
{
|
||||
$this->sheetView = $sheetView;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSheetView(): bool
|
||||
{
|
||||
return $this->sheetView instanceof SheetView;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ class Worksheet
|
||||
/** @var int Index of the last written row */
|
||||
private $lastWrittenRowIndex;
|
||||
|
||||
/** @var bool */
|
||||
private $hasStarted = false;
|
||||
|
||||
/**
|
||||
* Worksheet constructor.
|
||||
*
|
||||
@ -110,4 +113,20 @@ class Worksheet
|
||||
// sheet index is zero-based, while ID is 1-based
|
||||
return $this->externalSheet->getIndex() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasStarted(): bool
|
||||
{
|
||||
return $this->hasStarted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $hasStarted
|
||||
*/
|
||||
public function setHasStarted(bool $hasStarted = true): void
|
||||
{
|
||||
$this->hasStarted = $hasStarted;
|
||||
}
|
||||
}
|
||||
|
@ -131,8 +131,6 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
|
||||
$worksheetFilePath = $this->getWorksheetFilePath($sheet);
|
||||
$worksheet = $this->entityFactory->createWorksheet($worksheetFilePath, $sheet);
|
||||
|
||||
$this->worksheetManager->startSheet($worksheet);
|
||||
|
||||
$worksheets[] = $worksheet;
|
||||
$this->workbook->setWorksheets($worksheets);
|
||||
|
||||
@ -223,8 +221,15 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
|
||||
if ($hasReachedMaxRows) {
|
||||
// ... continue writing in a new sheet if option set
|
||||
if ($this->optionsManager->getOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY)) {
|
||||
$previousSheet = $currentWorksheet->getExternalSheet();
|
||||
$currentWorksheet = $this->addNewSheetAndMakeItCurrent();
|
||||
|
||||
// Use the same sheet view if set
|
||||
$sheet = $currentWorksheet->getExternalSheet();
|
||||
if ($previousSheet->hasSheetView()) {
|
||||
$sheet->setSheetView($previousSheet->getSheetView());
|
||||
}
|
||||
|
||||
$this->addRowToWorksheet($currentWorksheet, $row);
|
||||
} else {
|
||||
// otherwise, do nothing as the data won't be written anyways
|
||||
|
295
src/Spout/Writer/XLSX/Entity/SheetView.php
Normal file
295
src/Spout/Writer/XLSX/Entity/SheetView.php
Normal file
@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
namespace Box\Spout\Writer\XLSX\Entity;
|
||||
|
||||
use Box\Spout\Common\Exception\InvalidArgumentException;
|
||||
use Box\Spout\Reader\XLSX\Helper\CellHelper;
|
||||
|
||||
class SheetView
|
||||
{
|
||||
/** @var bool */
|
||||
protected $showFormulas = false;
|
||||
|
||||
/** @var bool */
|
||||
protected $showGridLines = true;
|
||||
|
||||
/** @var bool */
|
||||
protected $showRowcolHeaders = true;
|
||||
|
||||
/** @var bool */
|
||||
protected $showZeroes = true;
|
||||
|
||||
/** @var bool */
|
||||
protected $rightToLeft = false;
|
||||
|
||||
/** @var bool */
|
||||
protected $tabSelected = false;
|
||||
|
||||
/** @var bool */
|
||||
protected $showOutlineSymbols = true;
|
||||
|
||||
/** @var bool */
|
||||
protected $defaultGridColor = true;
|
||||
|
||||
/** @var string */
|
||||
protected $view = 'normal';
|
||||
|
||||
/** @var string */
|
||||
protected $topLeftCell = 'A1';
|
||||
|
||||
/** @var int */
|
||||
protected $colorId = 64;
|
||||
|
||||
/** @var int */
|
||||
protected $zoomScale = 100;
|
||||
|
||||
/** @var int */
|
||||
protected $zoomScaleNormal = 100;
|
||||
|
||||
/** @var int */
|
||||
protected $zoomScalePageLayoutView = 100;
|
||||
|
||||
/** @var int */
|
||||
protected $workbookViewId = 0;
|
||||
|
||||
/** @var int */
|
||||
protected $freezeRow = 0;
|
||||
|
||||
/** @var string */
|
||||
protected $freezeColumn = 'A';
|
||||
|
||||
/**
|
||||
* @param bool $showFormulas
|
||||
* @return $this
|
||||
*/
|
||||
public function setShowFormulas(bool $showFormulas): self
|
||||
{
|
||||
$this->showFormulas = $showFormulas;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $showGridLines
|
||||
* @return $this
|
||||
*/
|
||||
public function setShowGridLines(bool $showGridLines): self
|
||||
{
|
||||
$this->showGridLines = $showGridLines;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $showRowcolHeaders
|
||||
* @return $this
|
||||
*/
|
||||
public function setShowRowcolHeaders(bool $showRowcolHeaders): self
|
||||
{
|
||||
$this->showRowcolHeaders = $showRowcolHeaders;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $showZeroes
|
||||
* @return $this
|
||||
*/
|
||||
public function setShowZeroes(bool $showZeroes): self
|
||||
{
|
||||
$this->showZeroes = $showZeroes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $rightToLeft
|
||||
* @return $this
|
||||
*/
|
||||
public function setRightToLeft(bool $rightToLeft): self
|
||||
{
|
||||
$this->rightToLeft = $rightToLeft;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $tabSelected
|
||||
* @return $this
|
||||
*/
|
||||
public function setTabSelected(bool $tabSelected): self
|
||||
{
|
||||
$this->tabSelected = $tabSelected;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $showOutlineSymbols
|
||||
* @return $this
|
||||
*/
|
||||
public function setShowOutlineSymbols(bool $showOutlineSymbols): self
|
||||
{
|
||||
$this->showOutlineSymbols = $showOutlineSymbols;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $defaultGridColor
|
||||
* @return $this
|
||||
*/
|
||||
public function setDefaultGridColor(bool $defaultGridColor): self
|
||||
{
|
||||
$this->defaultGridColor = $defaultGridColor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $view
|
||||
* @return $this
|
||||
*/
|
||||
public function setView(string $view): self
|
||||
{
|
||||
$this->view = $view;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $topLeftCell
|
||||
* @return $this
|
||||
*/
|
||||
public function setTopLeftCell(string $topLeftCell): self
|
||||
{
|
||||
$this->topLeftCell = $topLeftCell;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $colorId
|
||||
* @return $this
|
||||
*/
|
||||
public function setColorId(int $colorId): self
|
||||
{
|
||||
$this->colorId = $colorId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $zoomScale
|
||||
* @return $this
|
||||
*/
|
||||
public function setZoomScale(int $zoomScale): self
|
||||
{
|
||||
$this->zoomScale = $zoomScale;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $zoomScaleNormal
|
||||
* @return $this
|
||||
*/
|
||||
public function setZoomScaleNormal(int $zoomScaleNormal): self
|
||||
{
|
||||
$this->zoomScaleNormal = $zoomScaleNormal;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $zoomScalePageLayoutView
|
||||
* @return $this
|
||||
*/
|
||||
public function setZoomScalePageLayoutView(int $zoomScalePageLayoutView): self
|
||||
{
|
||||
$this->zoomScalePageLayoutView = $zoomScalePageLayoutView;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $workbookViewId
|
||||
* @return $this
|
||||
*/
|
||||
public function setWorkbookViewId(int $workbookViewId): self
|
||||
{
|
||||
$this->workbookViewId = $workbookViewId;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $freezeRow Set to 2 to fix the first row
|
||||
* @return $this
|
||||
*/
|
||||
public function setFreezeRow(int $freezeRow): self
|
||||
{
|
||||
if ($freezeRow < 1) {
|
||||
throw new InvalidArgumentException('Freeze row must be a positive integer', 1589543073);
|
||||
}
|
||||
|
||||
$this->freezeRow = $freezeRow;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $freezeColumn Set to B to fix the first column
|
||||
* @return $this
|
||||
*/
|
||||
public function setFreezeColumn(string $freezeColumn): self
|
||||
{
|
||||
$this->freezeColumn = strtoupper($freezeColumn);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getXml(): string
|
||||
{
|
||||
return '<sheetView' . $this->getSheetViewAttributes() . '>' .
|
||||
$this->getFreezeCellPaneXml() .
|
||||
'</sheetView>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getSheetViewAttributes(): string
|
||||
{
|
||||
// Get class properties
|
||||
$propertyValues = get_object_vars($this);
|
||||
unset($propertyValues['freezeRow'], $propertyValues['freezeColumn']);
|
||||
|
||||
return $this->generateAttributes($propertyValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getFreezeCellPaneXml(): string
|
||||
{
|
||||
if ($this->freezeRow < 2 && $this->freezeColumn === 'A') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$columnIndex = CellHelper::getColumnIndexFromCellIndex($this->freezeColumn . '1');
|
||||
|
||||
return '<pane' . $this->generateAttributes([
|
||||
'xSplit' => $columnIndex,
|
||||
'ySplit' => $this->freezeRow - 1,
|
||||
'topLeftCell' => $this->freezeColumn . $this->freezeRow,
|
||||
'activePane' => 'bottomRight',
|
||||
'state' => 'frozen',
|
||||
]) . '/>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data with key containing the attribute name and value containing the attribute value
|
||||
* @return string
|
||||
*/
|
||||
protected function generateAttributes(array $data): string
|
||||
{
|
||||
// Create attribute for each key
|
||||
$attributes = array_map(function ($key, $value) {
|
||||
if (is_bool($value)) {
|
||||
$value = $value ? 'true' : 'false';
|
||||
}
|
||||
|
||||
return $key . '="' . $value . '"';
|
||||
}, array_keys($data), $data);
|
||||
|
||||
// Append all attributes
|
||||
return ' ' . implode(' ', $attributes);
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ use Box\Spout\Writer\Common\Helper\CellHelper;
|
||||
use Box\Spout\Writer\Common\Manager\RowManager;
|
||||
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
|
||||
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
|
||||
use Box\Spout\Writer\XLSX\Entity\SheetView;
|
||||
use Box\Spout\Writer\XLSX\Manager\Style\StyleManager;
|
||||
|
||||
/**
|
||||
@ -107,13 +108,34 @@ EOD;
|
||||
*/
|
||||
public function startSheet(Worksheet $worksheet)
|
||||
{
|
||||
// Only start once
|
||||
if ($worksheet->hasStarted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
|
||||
$this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
|
||||
|
||||
$worksheet->setFilePointer($sheetFilePointer);
|
||||
|
||||
\fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER);
|
||||
$this->addSheetViews($sheetFilePointer, $worksheet);
|
||||
\fwrite($sheetFilePointer, '<sheetData>');
|
||||
|
||||
$worksheet->setHasStarted();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $sheetFilePointer
|
||||
* @param resourceWorksheet $worksheet
|
||||
* @return void
|
||||
*/
|
||||
private function addSheetViews($sheetFilePointer, Worksheet $worksheet): void
|
||||
{
|
||||
$sheet = $worksheet->getExternalSheet();
|
||||
if ($sheet->hasSheetView()) {
|
||||
\fwrite($sheetFilePointer, '<sheetViews>' . $sheet->getSheetView()->getXml() . '</sheetViews>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -135,6 +157,9 @@ EOD;
|
||||
*/
|
||||
public function addRow(Worksheet $worksheet, Row $row)
|
||||
{
|
||||
// Start the sheet when the first row is added
|
||||
$this->startSheet($worksheet);
|
||||
|
||||
if (!$this->rowManager->isEmpty($row)) {
|
||||
$this->addNonEmptyRow($worksheet, $row);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user