Support workbook view attributes for Xlsx format

Editing a Xlsx document using PhpSpreadsheet should preserve the workbook
view attributes of that document. For example, if the worksheet tabs are
hidden in the original document, they should remain hidden after updating.

Fixes #523
Fixes #525
This commit is contained in:
Bill Blume 2018-06-24 20:09:32 +09:00 committed by Adrien Crivelli
parent 7a4cbd4fd5
commit edb68ce05c
No known key found for this signature in database
GPG Key ID: B182FD79DC6DE92E
5 changed files with 429 additions and 10 deletions

View File

@ -7,11 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased] ## [Unreleased]
- Cell formats with escaped spaces were causing incorrect date formatting - [#557](https://github.com/PHPOffice/PhpSpreadsheet/issues/557) ### Added
- Support workbook view attributes for Xlsx format - [#523](https://github.com/PHPOffice/PhpSpreadsheet/issues/523)
### Fixed ### Fixed
- Xlsx reader crashed when reading a file with workbook protection - [#553](https://github.com/PHPOffice/PhpSpreadsheet/pull/553) - Xlsx reader crashed when reading a file with workbook protection - [#553](https://github.com/PHPOffice/PhpSpreadsheet/pull/553)
- Cell formats with escaped spaces were causing incorrect date formatting - [#557](https://github.com/PHPOffice/PhpSpreadsheet/issues/557)
## [1.3.1] - 2018-06-12 ## [1.3.1] - 2018-06-12

View File

@ -1973,8 +1973,10 @@ class Xlsx extends BaseReader
} }
if ((!$this->readDataOnly) || (!empty($this->loadSheetsOnly))) { if ((!$this->readDataOnly) || (!empty($this->loadSheetsOnly))) {
$workbookView = $xmlWorkbook->bookViews->workbookView;
// active sheet index // active sheet index
$activeTab = (int) ($xmlWorkbook->bookViews->workbookView['activeTab']); // refers to old sheet index $activeTab = (int) ($workbookView['activeTab']); // refers to old sheet index
// keep active sheet index if sheet is still loaded, else first sheet is set as the active // keep active sheet index if sheet is still loaded, else first sheet is set as the active
if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) { if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) {
@ -1985,6 +1987,46 @@ class Xlsx extends BaseReader
} }
$excel->setActiveSheetIndex(0); $excel->setActiveSheetIndex(0);
} }
if (isset($workbookView['showHorizontalScroll'])) {
$showHorizontalScroll = (string) $workbookView['showHorizontalScroll'];
$excel->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll));
}
if (isset($workbookView['showVerticalScroll'])) {
$showVerticalScroll = (string) $workbookView['showVerticalScroll'];
$excel->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll));
}
if (isset($workbookView['showSheetTabs'])) {
$showSheetTabs = (string) $workbookView['showSheetTabs'];
$excel->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs));
}
if (isset($workbookView['minimized'])) {
$minimized = (string) $workbookView['minimized'];
$excel->setMinimized($this->castXsdBooleanToBool($minimized));
}
if (isset($workbookView['autoFilterDateGrouping'])) {
$autoFilterDateGrouping = (string) $workbookView['autoFilterDateGrouping'];
$excel->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping));
}
if (isset($workbookView['firstSheet'])) {
$firstSheet = (string) $workbookView['firstSheet'];
$excel->setFirstSheetIndex((int) $firstSheet);
}
if (isset($workbookView['visibility'])) {
$visibility = (string) $workbookView['visibility'];
$excel->setVisibility($visibility);
}
if (isset($workbookView['tabRatio'])) {
$tabRatio = (string) $workbookView['tabRatio'];
$excel->setTabRatio((int) $tabRatio);
}
} }
break; break;
@ -2475,4 +2517,27 @@ class Xlsx extends BaseReader
} }
unset($unparsedPrinterSettings); unset($unparsedPrinterSettings);
} }
/**
* Convert an 'xsd:boolean' XML value to a PHP boolean value.
* A valid 'xsd:boolean' XML value can be one of the following
* four values: 'true', 'false', '1', '0'. It is case sensitive.
*
* Note that just doing '(bool) $xsdBoolean' is not safe,
* since '(bool) "false"' returns true.
*
* @see https://www.w3.org/TR/xmlschema11-2/#boolean
*
* @param string $xsdBoolean An XML string value of type 'xsd:boolean'
*
* @return bool Boolean value
*/
private function castXsdBooleanToBool($xsdBoolean)
{
if ($xsdBoolean === 'false') {
return false;
}
return (bool) $xsdBoolean;
}
} }

View File

@ -9,6 +9,17 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Spreadsheet class Spreadsheet
{ {
// Allowable values for workbook window visilbity
const VISIBILITY_VISIBLE = 'visible';
const VISIBILITY_HIDDEN = 'hidden';
const VISIBILITY_VERY_HIDDEN = 'veryHidden';
private static $workbookViewVisibilityValues = [
self::VISIBILITY_VISIBLE,
self::VISIBILITY_HIDDEN,
self::VISIBILITY_VERY_HIDDEN,
];
/** /**
* Unique ID. * Unique ID.
* *
@ -123,6 +134,67 @@ class Spreadsheet
*/ */
private $unparsedLoadedData = []; private $unparsedLoadedData = [];
/**
* Controls visibility of the horizonal scroll bar in the application.
*
* @var bool
*/
private $showHorizontalScroll = true;
/**
* Controls visibility of the horizonal scroll bar in the application.
*
* @var bool
*/
private $showVerticalScroll = true;
/**
* Controls visibility of the sheet tabs in the application.
*
* @var bool
*/
private $showSheetTabs = true;
/**
* Specifies a boolean value that indicates whether the workbook window
* is minimized.
*
* @var bool
*/
private $minimized = false;
/**
* Specifies a boolean value that indicates whether to group dates
* when presenting the user with filtering optiomd in the user
* interface.
*
* @var bool
*/
private $autoFilterDateGrouping = true;
/**
* Specifies the index to the first sheet in the book view.
*
* @var int
*/
private $firstSheetIndex = 0;
/**
* Specifies the visible status of the workbook.
*
* @var string
*/
private $visibility = self::VISIBILITY_VISIBLE;
/**
* Specifies the ratio between the workbook tabs bar and the horizontal
* scroll bar. TabRatio is assumed to be out of 1000 of the horizontal
* window width.
*
* @var int
*/
private $tabRatio = 600;
/** /**
* The workbook has macros ? * The workbook has macros ?
* *
@ -1216,4 +1288,203 @@ class Spreadsheet
{ {
return $this->uniqueID; return $this->uniqueID;
} }
/**
* Get the visibility of the horizonal scroll bar in the application.
*
* @return bool True if horizonal scroll bar is visible
*/
public function getShowHorizontalScroll()
{
return $this->showHorizontalScroll;
}
/**
* Set the visibility of the horizonal scroll bar in the application.
*
* @param bool $showHorizontalScroll True if horizonal scroll bar is visible
*/
public function setShowHorizontalScroll($showHorizontalScroll)
{
$this->showHorizontalScroll = (bool) $showHorizontalScroll;
}
/**
* Get the visibility of the vertical scroll bar in the application.
*
* @return bool True if vertical scroll bar is visible
*/
public function getShowVerticalScroll()
{
return $this->showVerticalScroll;
}
/**
* Set the visibility of the vertical scroll bar in the application.
*
* @param bool $showVerticalScroll True if vertical scroll bar is visible
*/
public function setShowVerticalScroll($showVerticalScroll)
{
$this->showVerticalScroll = (bool) $showVerticalScroll;
}
/**
* Get the visibility of the sheet tabs in the application.
*
* @return bool True if the sheet tabs are visible
*/
public function getShowSheetTabs()
{
return $this->showSheetTabs;
}
/**
* Set the visibility of the sheet tabs in the application.
*
* @param bool $showSheetTabs True if sheet tabs are visible
*/
public function setShowSheetTabs($showSheetTabs)
{
$this->showSheetTabs = (bool) $showSheetTabs;
}
/**
* Return whether the workbook window is minimized.
*
* @return bool true if workbook window is minimized
*/
public function getMinimized()
{
return $this->minimized;
}
/**
* Set whether the workbook window is minimized.
*
* @param bool $minimized true if workbook window is minimized
*/
public function setMinimized($minimized)
{
$this->minimized = (bool) $minimized;
}
/**
* Return whether to group dates when presenting the user with
* filtering optiomd in the user interface.
*
* @return bool true if workbook window is minimized
*/
public function getAutoFilterDateGrouping()
{
return $this->autoFilterDateGrouping;
}
/**
* Set whether to group dates when presenting the user with
* filtering optiomd in the user interface.
*
* @param bool $autoFilterDateGrouping true if workbook window is minimized
*/
public function setAutoFilterDateGrouping($autoFilterDateGrouping)
{
$this->autoFilterDateGrouping = (bool) $autoFilterDateGrouping;
}
/**
* Return the first sheet in the book view.
*
* @return int First sheet in book view
*/
public function getFirstSheetIndex()
{
return $this->firstSheetIndex;
}
/**
* Set the first sheet in the book view.
*
* @param int $firstSheetIndex First sheet in book view
*
* @throws Exception if the given value is invalid
*/
public function setFirstSheetIndex($firstSheetIndex)
{
if ($firstSheetIndex >= 0) {
$this->firstSheetIndex = (int) $firstSheetIndex;
} else {
throw new Exception('First sheet index must be a positive integer.');
}
}
/**
* Return the visibility status of the workbook.
*
* This may be one of the following three values:
* - visibile
*
* @return string Visible status
*/
public function getVisibility()
{
return $this->visibility;
}
/**
* Set the visibility status of the workbook.
*
* Valid values are:
* - 'visible' (self::VISIBILITY_VISIBLE):
* Workbook window is visible
* - 'hidden' (self::VISIBILITY_HIDDEN):
* Workbook window is hidden, but can be shown by the user
* via the user interface
* - 'veryHidden' (self::VISIBILITY_VERY_HIDDEN):
* Workbook window is hidden and cannot be shown in the
* user interface.
*
* @param string $visibility visibility status of the workbook
*
* @throws Exception if the given value is invalid
*/
public function setVisibility($visibility)
{
if ($visibility === null) {
$visibility = self::VISIBILITY_VISIBLE;
}
if (in_array($visibility, self::$workbookViewVisibilityValues)) {
$this->visibility = $visibility;
} else {
throw new Exception('Invalid visibility value.');
}
}
/**
* Get the ratio between the workbook tabs bar and the horizontal scroll bar.
* TabRatio is assumed to be out of 1000 of the horizontal window width.
*
* @return int Ratio between the workbook tabs bar and the horizontal scroll bar
*/
public function getTabRatio()
{
return $this->tabRatio;
}
/**
* Set the ratio between the workbook tabs bar and the horizontal scroll bar
* TabRatio is assumed to be out of 1000 of the horizontal window width.
*
* @param int $tabRatio Ratio between the tabs bar and the horizontal scroll bar
*
* @throws Exception if the given value is invalid
*/
public function setTabRatio($tabRatio)
{
if ($tabRatio >= 0 || $tabRatio <= 1000) {
$this->tabRatio = (int) $tabRatio;
} else {
throw new Exception('Tab ratio must be between 0 and 1000.');
}
}
} }

View File

@ -117,14 +117,14 @@ class Workbook extends WriterPart
$objWriter->startElement('workbookView'); $objWriter->startElement('workbookView');
$objWriter->writeAttribute('activeTab', $spreadsheet->getActiveSheetIndex()); $objWriter->writeAttribute('activeTab', $spreadsheet->getActiveSheetIndex());
$objWriter->writeAttribute('autoFilterDateGrouping', '1'); $objWriter->writeAttribute('autoFilterDateGrouping', ($spreadsheet->getAutoFilterDateGrouping() ? 'true' : 'false'));
$objWriter->writeAttribute('firstSheet', '0'); $objWriter->writeAttribute('firstSheet', $spreadsheet->getFirstSheetIndex());
$objWriter->writeAttribute('minimized', '0'); $objWriter->writeAttribute('minimized', ($spreadsheet->getMinimized() ? 'true' : 'false'));
$objWriter->writeAttribute('showHorizontalScroll', '1'); $objWriter->writeAttribute('showHorizontalScroll', ($spreadsheet->getShowHorizontalScroll() ? 'true' : 'false'));
$objWriter->writeAttribute('showSheetTabs', '1'); $objWriter->writeAttribute('showSheetTabs', ($spreadsheet->getShowSheetTabs() ? 'true' : 'false'));
$objWriter->writeAttribute('showVerticalScroll', '1'); $objWriter->writeAttribute('showVerticalScroll', ($spreadsheet->getShowVerticalScroll() ? 'true' : 'false'));
$objWriter->writeAttribute('tabRatio', '600'); $objWriter->writeAttribute('tabRatio', $spreadsheet->getTabRatio());
$objWriter->writeAttribute('visibility', 'visible'); $objWriter->writeAttribute('visibility', $spreadsheet->getVisibility());
$objWriter->endElement(); $objWriter->endElement();

View File

@ -0,0 +1,80 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Functional;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
class WorkbookViewAttributesTest extends AbstractFunctional
{
public function providerFormats()
{
return [
['Xlsx'],
];
}
/**
* Test that workbook bookview attributes such as 'showSheetTabs',
* (the attribute controlling worksheet tabs visibility,)
* are preserved when xlsx documents are read and written.
*
* @see https://github.com/PHPOffice/PhpSpreadsheet/issues/523
*
* @dataProvider providerFormats
*
* @param string $format
*/
public function testPreserveWorkbookViewAttributes($format)
{
// Create a dummy workbook with two worksheets
$workbook = new Spreadsheet();
$worksheet1 = $workbook->getActiveSheet();
$worksheet1->setTitle('Tweedledee');
$worksheet1->setCellValue('A1', 1);
$worksheet2 = $workbook->createSheet();
$worksheet2->setTitle('Tweeldedum');
$worksheet2->setCellValue('A1', 2);
// Check that the bookview attributes return default values
$this->assertTrue($workbook->getShowHorizontalScroll());
$this->assertTrue($workbook->getShowVerticalScroll());
$this->assertTrue($workbook->getShowSheetTabs());
$this->assertTrue($workbook->getAutoFilterDateGrouping());
$this->assertFalse($workbook->getMinimized());
$this->assertSame(0, $workbook->getFirstSheetIndex());
$this->assertSame(600, $workbook->getTabRatio());
$this->assertSame(Spreadsheet::VISIBILITY_VISIBLE, $workbook->getVisibility());
// Set the bookview attributes to non-default values
$workbook->setShowHorizontalScroll(false);
$workbook->setShowVerticalScroll(false);
$workbook->setShowSheetTabs(false);
$workbook->setAutoFilterDateGrouping(false);
$workbook->setMinimized(true);
$workbook->setFirstSheetIndex(1);
$workbook->setTabRatio(700);
$workbook->setVisibility(Spreadsheet::VISIBILITY_HIDDEN);
// Check that bookview attributes were set properly
$this->assertFalse($workbook->getShowHorizontalScroll());
$this->assertFalse($workbook->getShowVerticalScroll());
$this->assertFalse($workbook->getShowSheetTabs());
$this->assertFalse($workbook->getAutoFilterDateGrouping());
$this->assertTrue($workbook->getMinimized());
$this->assertSame(1, $workbook->getFirstSheetIndex());
$this->assertSame(700, $workbook->getTabRatio());
$this->assertSame(Spreadsheet::VISIBILITY_HIDDEN, $workbook->getVisibility());
$workbook2 = $this->writeAndReload($workbook, $format);
// Check that the read spreadsheet has the right bookview attributes
$this->assertFalse($workbook2->getShowHorizontalScroll());
$this->assertFalse($workbook2->getShowVerticalScroll());
$this->assertFalse($workbook2->getShowSheetTabs());
$this->assertFalse($workbook2->getAutoFilterDateGrouping());
$this->assertTrue($workbook2->getMinimized());
$this->assertSame(1, $workbook2->getFirstSheetIndex());
$this->assertSame(700, $workbook2->getTabRatio());
$this->assertSame(Spreadsheet::VISIBILITY_HIDDEN, $workbook2->getVisibility());
}
}