From b9206fcb4bc0aef122eabf127fb665da4b4e6fb4 Mon Sep 17 00:00:00 2001 From: Adrien Loison Date: Fri, 10 Nov 2017 23:45:07 +0100 Subject: [PATCH] Sheet visibility - XLSX writer and reader --- .../Reader/XLSX/Creator/EntityFactory.php | 4 +- .../Reader/XLSX/Manager/SheetManager.php | 9 ++++ src/Spout/Reader/XLSX/Sheet.php | 15 +++++- src/Spout/Writer/Common/Entity/Sheet.php | 23 +++++++++ .../Writer/XLSX/Helper/FileSystemHelper.php | 3 +- tests/Spout/Reader/XLSX/SheetTest.php | 11 +++++ tests/Spout/Writer/XLSX/SheetTest.php | 45 ++++++++++++++++-- .../xlsx/two_sheets_one_hidden_one_not.xlsx | Bin 0 -> 5928 bytes 8 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 tests/resources/xlsx/two_sheets_one_hidden_one_not.xlsx diff --git a/src/Spout/Reader/XLSX/Creator/EntityFactory.php b/src/Spout/Reader/XLSX/Creator/EntityFactory.php index 8699529..8ef648a 100644 --- a/src/Spout/Reader/XLSX/Creator/EntityFactory.php +++ b/src/Spout/Reader/XLSX/Creator/EntityFactory.php @@ -53,6 +53,7 @@ class EntityFactory implements EntityFactoryInterface * @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based) * @param string $sheetName Name of the sheet * @param bool $isSheetActive Whether the sheet was defined as active + * @param bool $isSheetVisible Whether the sheet is visible * @param \Box\Spout\Common\Manager\OptionsManagerInterface $optionsManager Reader's options manager * @param SharedStringsManager $sharedStringsManager Manages shared strings * @return Sheet @@ -63,12 +64,13 @@ class EntityFactory implements EntityFactoryInterface $sheetIndex, $sheetName, $isSheetActive, + $isSheetVisible, $optionsManager, $sharedStringsManager ) { $rowIterator = $this->createRowIterator($filePath, $sheetDataXMLFilePath, $optionsManager, $sharedStringsManager); - return new Sheet($rowIterator, $sheetIndex, $sheetName, $isSheetActive); + return new Sheet($rowIterator, $sheetIndex, $sheetName, $isSheetActive, $isSheetVisible); } /** diff --git a/src/Spout/Reader/XLSX/Manager/SheetManager.php b/src/Spout/Reader/XLSX/Manager/SheetManager.php index 1dbcc95..ac400e3 100644 --- a/src/Spout/Reader/XLSX/Manager/SheetManager.php +++ b/src/Spout/Reader/XLSX/Manager/SheetManager.php @@ -29,9 +29,13 @@ class SheetManager const XML_ATTRIBUTE_ACTIVE_TAB = 'activeTab'; const XML_ATTRIBUTE_R_ID = 'r:id'; const XML_ATTRIBUTE_NAME = 'name'; + const XML_ATTRIBUTE_STATE = 'state'; const XML_ATTRIBUTE_ID = 'Id'; const XML_ATTRIBUTE_TARGET = 'Target'; + /** State value to represent a hidden sheet */ + const SHEET_STATE_HIDDEN = 'hidden'; + /** @var string Path of the XLSX file being read */ protected $filePath; @@ -163,6 +167,10 @@ class SheetManager protected function getSheetFromSheetXMLNode($xmlReaderOnSheetNode, $sheetIndexZeroBased, $isSheetActive) { $sheetId = $xmlReaderOnSheetNode->getAttribute(self::XML_ATTRIBUTE_R_ID); + + $sheetState = $xmlReaderOnSheetNode->getAttribute(self::XML_ATTRIBUTE_STATE); + $isSheetVisible = ($sheetState !== self::SHEET_STATE_HIDDEN); + $escapedSheetName = $xmlReaderOnSheetNode->getAttribute(self::XML_ATTRIBUTE_NAME); $sheetName = $this->escaper->unescape($escapedSheetName); @@ -174,6 +182,7 @@ class SheetManager $sheetIndexZeroBased, $sheetName, $isSheetActive, + $isSheetVisible, $this->optionsManager, $this->sharedStringsManager ); diff --git a/src/Spout/Reader/XLSX/Sheet.php b/src/Spout/Reader/XLSX/Sheet.php index 592c7c3..575af2d 100644 --- a/src/Spout/Reader/XLSX/Sheet.php +++ b/src/Spout/Reader/XLSX/Sheet.php @@ -22,18 +22,23 @@ class Sheet implements SheetInterface /** @var bool Whether the sheet was the active one */ protected $isActive; + /** @var bool Whether the sheet is visible */ + protected $isVisible; + /** * @param RowIterator $rowIterator The corresponding row iterator * @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based) * @param string $sheetName Name of the sheet * @param bool $isSheetActive Whether the sheet was defined as active + * @param bool $isSheetVisible Whether the sheet is visible */ - public function __construct($rowIterator, $sheetIndex, $sheetName, $isSheetActive) + public function __construct($rowIterator, $sheetIndex, $sheetName, $isSheetActive, $isSheetVisible) { $this->rowIterator = $rowIterator; $this->index = $sheetIndex; $this->name = $sheetName; $this->isActive = $isSheetActive; + $this->isVisible = $isSheetVisible; } /** @@ -67,4 +72,12 @@ class Sheet implements SheetInterface { return $this->isActive; } + + /** + * @return bool Whether the sheet is visible + */ + public function isVisible() + { + return $this->isVisible; + } } diff --git a/src/Spout/Writer/Common/Entity/Sheet.php b/src/Spout/Writer/Common/Entity/Sheet.php index 5713175..c2f5366 100644 --- a/src/Spout/Writer/Common/Entity/Sheet.php +++ b/src/Spout/Writer/Common/Entity/Sheet.php @@ -21,6 +21,9 @@ class Sheet /** @var string Name of the sheet */ private $name; + /** @var bool Visibility of the sheet */ + private $isVisible; + /** @var SheetManager Sheet manager */ private $sheetManager; @@ -38,6 +41,7 @@ class Sheet $this->sheetManager->markWorkbookIdAsUsed($associatedWorkbookId); $this->setName(self::DEFAULT_SHEET_NAME_PREFIX . ($sheetIndex + 1)); + $this->setIsVisible(true); } /** @@ -85,4 +89,23 @@ class Sheet return $this; } + + /** + * @return bool isVisible Visibility of the sheet + */ + public function isVisible() + { + return $this->isVisible; + } + + /** + * @param bool $isVisible Visibility of the sheet + * @return Sheet + */ + public function setIsVisible($isVisible) + { + $this->isVisible = $isVisible; + + return $this; + } } diff --git a/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php b/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php index 8bba670..06d5481 100644 --- a/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php +++ b/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php @@ -307,8 +307,9 @@ EOD; /** @var Worksheet $worksheet */ foreach ($worksheets as $worksheet) { $worksheetName = $worksheet->getExternalSheet()->getName(); + $worksheetVisibility = $worksheet->getExternalSheet()->isVisible() ? 'visible' : 'hidden'; $worksheetId = $worksheet->getId(); - $workbookXmlFileContents .= ''; + $workbookXmlFileContents .= ''; } $workbookXmlFileContents .= <<<'EOD' diff --git a/tests/Spout/Reader/XLSX/SheetTest.php b/tests/Spout/Reader/XLSX/SheetTest.php index b4291fb..96eee4a 100644 --- a/tests/Spout/Reader/XLSX/SheetTest.php +++ b/tests/Spout/Reader/XLSX/SheetTest.php @@ -30,6 +30,17 @@ class SheetTest extends \PHPUnit_Framework_TestCase $this->assertTrue($sheets[1]->isActive()); } + /** + * @return void + */ + public function testReaderShouldReturnCorrectSheetVisibility() + { + $sheets = $this->openFileAndReturnSheets('two_sheets_one_hidden_one_not.xlsx'); + + $this->assertFalse($sheets[0]->isVisible()); + $this->assertTrue($sheets[1]->isVisible()); + } + /** * @param string $fileName * @return Sheet[] diff --git a/tests/Spout/Writer/XLSX/SheetTest.php b/tests/Spout/Writer/XLSX/SheetTest.php index ec636ad..64b276b 100644 --- a/tests/Spout/Writer/XLSX/SheetTest.php +++ b/tests/Spout/Writer/XLSX/SheetTest.php @@ -22,7 +22,7 @@ class SheetTest extends \PHPUnit_Framework_TestCase */ public function testGetSheetIndex() { - $sheets = $this->writeDataToMulitpleSheetsAndReturnSheets('test_get_sheet_index.xlsx'); + $sheets = $this->writeDataToMultipleSheetsAndReturnSheets('test_get_sheet_index.xlsx'); $this->assertEquals(2, count($sheets), '2 sheets should have been created'); $this->assertEquals(0, $sheets[0]->getIndex(), 'The first sheet should be index 0'); @@ -34,7 +34,7 @@ class SheetTest extends \PHPUnit_Framework_TestCase */ public function testGetSheetName() { - $sheets = $this->writeDataToMulitpleSheetsAndReturnSheets('test_get_sheet_name.xlsx'); + $sheets = $this->writeDataToMultipleSheetsAndReturnSheets('test_get_sheet_name.xlsx'); $this->assertEquals(2, count($sheets), '2 sheets should have been created'); $this->assertEquals('Sheet1', $sheets[0]->getName(), 'Invalid name for the first sheet'); @@ -48,7 +48,7 @@ class SheetTest extends \PHPUnit_Framework_TestCase { $fileName = 'test_set_name_should_create_sheet_with_custom_name.xlsx'; $customSheetName = 'CustomName'; - $this->writeDataAndReturnSheetWithCustomName($fileName, $customSheetName); + $this->writeDataToSheetWithCustomName($fileName, $customSheetName); $this->assertSheetNameEquals($customSheetName, $fileName, "The sheet name should have been changed to '$customSheetName'"); } @@ -78,12 +78,27 @@ class SheetTest extends \PHPUnit_Framework_TestCase $sheet->setName($customSheetName); } + /** + * @return void + */ + public function testSetSheetVisibilityShouldCreateSheetHidden() + { + $fileName = 'test_set_visibility_should_create_sheet_hidden.xlsx'; + $this->writeDataToHiddenSheet($fileName); + + $resourcePath = $this->getGeneratedResourcePath($fileName); + $pathToWorkbookFile = $resourcePath . '#xl/workbook.xml'; + $xmlContents = file_get_contents('zip://' . $pathToWorkbookFile); + + $this->assertContains(" state=\"hidden\"", $xmlContents, 'The sheet visibility should have been changed to "hidden"'); + } + /** * @param string $fileName * @param string $sheetName * @return Sheet */ - private function writeDataAndReturnSheetWithCustomName($fileName, $sheetName) + private function writeDataToSheetWithCustomName($fileName, $sheetName) { $this->createGeneratedFolderIfNeeded($fileName); $resourcePath = $this->getGeneratedResourcePath($fileName); @@ -105,7 +120,7 @@ class SheetTest extends \PHPUnit_Framework_TestCase * @param string $fileName * @return Sheet[] */ - private function writeDataToMulitpleSheetsAndReturnSheets($fileName) + private function writeDataToMultipleSheetsAndReturnSheets($fileName) { $this->createGeneratedFolderIfNeeded($fileName); $resourcePath = $this->getGeneratedResourcePath($fileName); @@ -123,6 +138,26 @@ class SheetTest extends \PHPUnit_Framework_TestCase return $writer->getSheets(); } + /** + * @param string $fileName + * @return void + */ + private function writeDataToHiddenSheet($fileName) + { + $this->createGeneratedFolderIfNeeded($fileName); + $resourcePath = $this->getGeneratedResourcePath($fileName); + + /** @var \Box\Spout\Writer\XLSX\Writer $writer */ + $writer = EntityFactory::createWriter(Type::XLSX); + $writer->openToFile($resourcePath); + + $sheet = $writer->getCurrentSheet(); + $sheet->setIsVisible(false); + + $writer->addRow($this->createRowFromValues(['xlsx--11', 'xlsx--12'])); + $writer->close(); + } + /** * @param string $expectedName * @param string $fileName diff --git a/tests/resources/xlsx/two_sheets_one_hidden_one_not.xlsx b/tests/resources/xlsx/two_sheets_one_hidden_one_not.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f6d571fd2747c0e774be5d8e13f4d2e47efe7098 GIT binary patch literal 5928 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 Ux|`Ag0JOVD`tGuZ2<}(^0(%8B>i_@% literal 0 HcmV?d00001