diff --git a/src/Spout/Reader/ODS/Creator/EntityFactory.php b/src/Spout/Reader/ODS/Creator/EntityFactory.php index 3af756a..0324dd8 100644 --- a/src/Spout/Reader/ODS/Creator/EntityFactory.php +++ b/src/Spout/Reader/ODS/Creator/EntityFactory.php @@ -45,14 +45,15 @@ 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 * @return Sheet */ - public function createSheet($xmlReader, $sheetIndex, $sheetName, $isSheetActive, $optionsManager) + public function createSheet($xmlReader, $sheetIndex, $sheetName, $isSheetActive, $isSheetVisible, $optionsManager) { $rowIterator = $this->createRowIterator($xmlReader, $optionsManager); - return new Sheet($rowIterator, $sheetIndex, $sheetName, $isSheetActive); + return new Sheet($rowIterator, $sheetIndex, $sheetName, $isSheetActive, $isSheetVisible); } /** diff --git a/src/Spout/Reader/ODS/Sheet.php b/src/Spout/Reader/ODS/Sheet.php index e696a1a..74ec61f 100644 --- a/src/Spout/Reader/ODS/Sheet.php +++ b/src/Spout/Reader/ODS/Sheet.php @@ -25,18 +25,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; } /** @@ -70,4 +75,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/Reader/ODS/SheetIterator.php b/src/Spout/Reader/ODS/SheetIterator.php index 3c6dd4e..6785b36 100644 --- a/src/Spout/Reader/ODS/SheetIterator.php +++ b/src/Spout/Reader/ODS/SheetIterator.php @@ -17,9 +17,16 @@ class SheetIterator implements IteratorInterface { const CONTENT_XML_FILE_PATH = 'content.xml'; + const XML_STYLE_NAMESPACE = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'; + /** Definition of XML nodes name and attribute used to parse sheet data */ + const XML_NODE_AUTOMATIC_STYLES = 'office:automatic-styles'; + const XML_NODE_STYLE_TABLE_PROPERTIES = 'table-properties'; const XML_NODE_TABLE = 'table:table'; + const XML_ATTRIBUTE_STYLE_NAME = 'style:name'; const XML_ATTRIBUTE_TABLE_NAME = 'table:name'; + const XML_ATTRIBUTE_TABLE_STYLE_NAME = 'table:style-name'; + const XML_ATTRIBUTE_TABLE_DISPLAY = 'table:display'; /** @var string $filePath Path of the file to be read */ protected $filePath; @@ -45,6 +52,9 @@ class SheetIterator implements IteratorInterface /** @var string The name of the sheet that was defined as active */ protected $activeSheetName; + /** @var array Associative array [STYLE_NAME] => [IS_SHEET_VISIBLE] */ + protected $sheetsVisibility; + /** * @param string $filePath Path of the file to be read * @param \Box\Spout\Common\Manager\OptionsManagerInterface $optionsManager @@ -79,6 +89,7 @@ class SheetIterator implements IteratorInterface } try { + $this->sheetsVisibility = $this->readSheetsVisibility(); $this->hasFoundSheet = $this->xmlReader->readUntilNodeFound(self::XML_NODE_TABLE); } catch (XMLProcessingException $exception) { throw new IOException("The content.xml file is invalid and cannot be read. [{$exception->getMessage()}]"); @@ -87,6 +98,33 @@ class SheetIterator implements IteratorInterface $this->currentSheetIndex = 0; } + /** + * Extracts the visibility of the sheets + * + * @return array Associative array [STYLE_NAME] => [IS_SHEET_VISIBLE] + */ + private function readSheetsVisibility() + { + $sheetsVisibility = []; + + $this->xmlReader->readUntilNodeFound(self::XML_NODE_AUTOMATIC_STYLES); + $automaticStylesNode = $this->xmlReader->expand(); + + $tableStyleNodes = $automaticStylesNode->getElementsByTagNameNS(self::XML_STYLE_NAMESPACE, self::XML_NODE_STYLE_TABLE_PROPERTIES); + + /** @var \DOMElement $tableStyleNode */ + foreach ($tableStyleNodes as $tableStyleNode) { + $isSheetVisible = ($tableStyleNode->getAttribute(self::XML_ATTRIBUTE_TABLE_DISPLAY) !== 'false'); + + $parentStyleNode = $tableStyleNode->parentNode; + $styleName = $parentStyleNode->getAttribute(self::XML_ATTRIBUTE_STYLE_NAME); + + $sheetsVisibility[$styleName] = $isSheetVisible; + } + + return $sheetsVisibility; + } + /** * Checks if current position is valid * @see http://php.net/manual/en/iterator.valid.php @@ -123,9 +161,20 @@ class SheetIterator implements IteratorInterface { $escapedSheetName = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_TABLE_NAME); $sheetName = $this->escaper->unescape($escapedSheetName); - $isActiveSheet = $this->isActiveSheet($sheetName, $this->currentSheetIndex, $this->activeSheetName); - return $this->entityFactory->createSheet($this->xmlReader, $this->currentSheetIndex, $sheetName, $isActiveSheet, $this->optionsManager); + $isSheetActive = $this->isSheetActive($sheetName, $this->currentSheetIndex, $this->activeSheetName); + + $sheetStyleName = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_TABLE_STYLE_NAME); + $isSheetVisible = $this->isSheetVisible($sheetStyleName); + + return $this->entityFactory->createSheet( + $this->xmlReader, + $this->currentSheetIndex, + $sheetName, + $isSheetActive, + $isSheetVisible, + $this->optionsManager + ); } /** @@ -136,7 +185,7 @@ class SheetIterator implements IteratorInterface * @param string|null $activeSheetName Name of the sheet that was defined as active or NULL if none defined * @return bool Whether the current sheet was defined as the active one */ - private function isActiveSheet($sheetName, $sheetIndex, $activeSheetName) + private function isSheetActive($sheetName, $sheetIndex, $activeSheetName) { // The given sheet is active if its name matches the defined active sheet's name // or if no information about the active sheet was found, it defaults to the first sheet. @@ -146,6 +195,19 @@ class SheetIterator implements IteratorInterface ); } + /** + * Returns whether the current sheet is visible + * + * @param string $sheetStyleName Name of the sheet style + * @return bool Whether the current sheet is visible + */ + private function isSheetVisible($sheetStyleName) + { + return isset($this->sheetsVisibility[$sheetStyleName]) ? + $this->sheetsVisibility[$sheetStyleName] : + true; + } + /** * Return the key of the current element * @see http://php.net/manual/en/iterator.key.php diff --git a/src/Spout/Writer/Common/Entity/Sheet.php b/src/Spout/Writer/Common/Entity/Sheet.php index c2f5366..aabdd91 100644 --- a/src/Spout/Writer/Common/Entity/Sheet.php +++ b/src/Spout/Writer/Common/Entity/Sheet.php @@ -98,11 +98,11 @@ class Sheet return $this->isVisible; } - /** - * @param bool $isVisible Visibility of the sheet - * @return Sheet - */ - public function setIsVisible($isVisible) + /** + * @param bool $isVisible Visibility of the sheet + * @return Sheet + */ + public function setIsVisible($isVisible) { $this->isVisible = $isVisible; diff --git a/src/Spout/Writer/ODS/Helper/FileSystemHelper.php b/src/Spout/Writer/ODS/Helper/FileSystemHelper.php index 65ed815..d0e2e05 100644 --- a/src/Spout/Writer/ODS/Helper/FileSystemHelper.php +++ b/src/Spout/Writer/ODS/Helper/FileSystemHelper.php @@ -202,7 +202,7 @@ EOD; EOD; $contentXmlFileContents .= $styleManager->getContentXmlFontFaceSectionContent(); - $contentXmlFileContents .= $styleManager->getContentXmlAutomaticStylesSectionContent(count($worksheets)); + $contentXmlFileContents .= $styleManager->getContentXmlAutomaticStylesSectionContent($worksheets); $contentXmlFileContents .= ''; diff --git a/src/Spout/Writer/ODS/Manager/Style/StyleManager.php b/src/Spout/Writer/ODS/Manager/Style/StyleManager.php index c658b18..38b9eb7 100644 --- a/src/Spout/Writer/ODS/Manager/Style/StyleManager.php +++ b/src/Spout/Writer/ODS/Manager/Style/StyleManager.php @@ -3,6 +3,7 @@ namespace Box\Spout\Writer\ODS\Manager\Style; use Box\Spout\Writer\Common\Entity\Style\BorderPart; +use Box\Spout\Writer\Common\Entity\Worksheet; use Box\Spout\Writer\ODS\Helper\BorderHelper; /** @@ -149,10 +150,10 @@ EOD; /** * Returns the contents of the "" section, inside "content.xml" file. * - * @param int $numWorksheets Number of worksheets created + * @param Worksheet[] $worksheets * @return string */ - public function getContentXmlAutomaticStylesSectionContent($numWorksheets) + public function getContentXmlAutomaticStylesSectionContent($worksheets) { $content = ''; @@ -169,10 +170,13 @@ EOD; EOD; - for ($i = 1; $i <= $numWorksheets; $i++) { + foreach ($worksheets as $worksheet) { + $worksheetId = $worksheet->getId(); + $isSheetVisible = $worksheet->getExternalSheet()->isVisible() ? 'true' : 'false'; + $content .= << - + + EOD; } diff --git a/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php b/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php index 06d5481..8997b98 100644 --- a/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php +++ b/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php @@ -309,7 +309,7 @@ EOD; $worksheetName = $worksheet->getExternalSheet()->getName(); $worksheetVisibility = $worksheet->getExternalSheet()->isVisible() ? 'visible' : 'hidden'; $worksheetId = $worksheet->getId(); - $workbookXmlFileContents .= ''; + $workbookXmlFileContents .= ''; } $workbookXmlFileContents .= <<<'EOD' diff --git a/tests/Spout/Reader/ODS/SheetTest.php b/tests/Spout/Reader/ODS/SheetTest.php index e5dfdf8..d03a3ab 100644 --- a/tests/Spout/Reader/ODS/SheetTest.php +++ b/tests/Spout/Reader/ODS/SheetTest.php @@ -42,6 +42,17 @@ class SheetTest extends \PHPUnit_Framework_TestCase $this->assertFalse($sheets[1]->isActive()); } + /** + * @return void + */ + public function testReaderShouldReturnCorrectSheetVisibility() + { + $sheets = $this->openFileAndReturnSheets('two_sheets_one_hidden_one_not.ods'); + + $this->assertFalse($sheets[0]->isVisible()); + $this->assertTrue($sheets[1]->isVisible()); + } + /** * @param string $fileName * @return Sheet[] diff --git a/tests/Spout/Writer/ODS/SheetTest.php b/tests/Spout/Writer/ODS/SheetTest.php index 1599c8f..52cf7aa 100644 --- a/tests/Spout/Writer/ODS/SheetTest.php +++ b/tests/Spout/Writer/ODS/SheetTest.php @@ -78,6 +78,21 @@ 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); + $pathToContentFile = $resourcePath . '#content.xml'; + $xmlContents = file_get_contents('zip://' . $pathToContentFile); + + $this->assertContains(' table:display="false"', $xmlContents, 'The sheet visibility should have been changed to "hidden"'); + } + /** * @param string $fileName * @param string $sheetName @@ -121,6 +136,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\ODS\Writer $writer */ + $writer = EntityFactory::createWriter(Type::ODS); + $writer->openToFile($resourcePath); + + $sheet = $writer->getCurrentSheet(); + $sheet->setIsVisible(false); + + $writer->addRow($this->createRowFromValues(['ods--11', 'ods--12'])); + $writer->close(); + } + /** * @param string $expectedName * @param string $fileName diff --git a/tests/Spout/Writer/XLSX/SheetTest.php b/tests/Spout/Writer/XLSX/SheetTest.php index 64b276b..9e71563 100644 --- a/tests/Spout/Writer/XLSX/SheetTest.php +++ b/tests/Spout/Writer/XLSX/SheetTest.php @@ -90,7 +90,7 @@ class SheetTest extends \PHPUnit_Framework_TestCase $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"'); + $this->assertContains(' state="hidden"', $xmlContents, 'The sheet visibility should have been changed to "hidden"'); } /** diff --git a/tests/resources/ods/two_sheets_one_hidden_one_not.ods b/tests/resources/ods/two_sheets_one_hidden_one_not.ods new file mode 100644 index 0000000..2771f8c Binary files /dev/null and b/tests/resources/ods/two_sheets_one_hidden_one_not.ods differ