Skip to content
This repository has been archived by the owner on May 26, 2022. It is now read-only.

Commit

Permalink
Expose Sheet::isActive() to provide info about the last active sheet (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
adrilo committed Apr 15, 2017
1 parent 4a65466 commit 7f8b95b
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 28 deletions.
9 changes: 9 additions & 0 deletions README.md
Expand Up @@ -313,6 +313,15 @@ $sheet->setName('My custom name');
>
> Handling these restrictions is the developer's responsibility. Spout does not try to automatically change the sheet's name, as one may rely on this name to be exactly what was passed in.
Finally, it is possible to know which sheet was active when the spreadsheet was last saved. This can be useful if you are only interested in processing the one sheet that was last focused.
```php
foreach ($reader->getSheetIterator() as $sheet) {
// only process data for the active sheet
if ($sheet->isActive()) {
// do something...
}
}
```

### Fluent interface

Expand Down
27 changes: 27 additions & 0 deletions src/Spout/Reader/CSV/Sheet.php
Expand Up @@ -32,4 +32,31 @@ public function getRowIterator()
{
return $this->rowIterator;
}

/**
* @api
* @return int Index of the sheet
*/
public function getIndex()
{
return 0;
}

/**
* @api
* @return string Name of the sheet - empty string since CSV does not support that
*/
public function getName()
{
return '';
}

/**
* @api
* @return bool Always TRUE as there is only one sheet
*/
public function isActive()
{
return true;
}
}
51 changes: 51 additions & 0 deletions src/Spout/Reader/ODS/Helper/SettingsHelper.php
@@ -0,0 +1,51 @@
<?php

namespace Box\Spout\Reader\ODS\Helper;

use Box\Spout\Reader\Exception\XMLProcessingException;
use Box\Spout\Reader\Wrapper\XMLReader;

/**
* Class SettingsHelper
* This class provides helper functions to extract data from the "settings.xml" file.
*
* @package Box\Spout\Reader\ODS\Helper
*/
class SettingsHelper
{
const SETTINGS_XML_FILE_PATH = 'settings.xml';

/** Definition of XML nodes name and attribute used to parse settings data */
const XML_NODE_CONFIG_ITEM = 'config:config-item';
const XML_ATTRIBUTE_CONFIG_NAME = 'config:name';
const XML_ATTRIBUTE_VALUE_ACTIVE_TABLE = 'ActiveTable';

/**
* @param string $filePath Path of the file to be read
* @return string|null Name of the sheet that was defined as active or NULL if none found
*/
public function getActiveSheetName($filePath)
{
$xmlReader = new XMLReader();
if ($xmlReader->openFileInZip($filePath, self::SETTINGS_XML_FILE_PATH) === false) {
return null;
}

$activeSheetName = null;

try {
while ($xmlReader->readUntilNodeFound(self::XML_NODE_CONFIG_ITEM)) {
if ($xmlReader->getAttribute(self::XML_ATTRIBUTE_CONFIG_NAME) === self::XML_ATTRIBUTE_VALUE_ACTIVE_TABLE) {
$activeSheetName = $xmlReader->readString();
break;
}
}
} catch (XMLProcessingException $exception) {
// do nothing
}

$xmlReader->close();

return $activeSheetName;
}
}
18 changes: 16 additions & 2 deletions src/Spout/Reader/ODS/Sheet.php
Expand Up @@ -25,17 +25,22 @@ class Sheet implements SheetInterface
/** @var string Name of the sheet */
protected $name;

/** @var bool Whether the sheet was the active one */
protected $isActive;

/**
* @param XMLReader $xmlReader XML Reader, positioned on the "<table:table>" element
* @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
* @param \Box\Spout\Reader\ODS\ReaderOptions $options Reader's current options
* @param string $sheetName Name of the sheet
* @param bool $isSheetActive Whether the sheet was defined as active
* @param \Box\Spout\Reader\ODS\ReaderOptions $options Reader's current options
*/
public function __construct($xmlReader, $sheetIndex, $sheetName, $options)
public function __construct($xmlReader, $sheetIndex, $sheetName, $isSheetActive, $options)
{
$this->rowIterator = new RowIterator($xmlReader, $options);
$this->index = $sheetIndex;
$this->name = $sheetName;
$this->isActive = $isSheetActive;
}

/**
Expand Down Expand Up @@ -64,4 +69,13 @@ public function getName()
{
return $this->name;
}

/**
* @api
* @return bool Whether the sheet was defined as active
*/
public function isActive()
{
return $this->isActive;
}
}
28 changes: 27 additions & 1 deletion src/Spout/Reader/ODS/SheetIterator.php
Expand Up @@ -5,6 +5,7 @@
use Box\Spout\Common\Exception\IOException;
use Box\Spout\Reader\Exception\XMLProcessingException;
use Box\Spout\Reader\IteratorInterface;
use Box\Spout\Reader\ODS\Helper\SettingsHelper;
use Box\Spout\Reader\Wrapper\XMLReader;

/**
Expand Down Expand Up @@ -39,6 +40,9 @@ class SheetIterator implements IteratorInterface
/** @var int The index of the sheet being read (zero-based) */
protected $currentSheetIndex;

/** @var string The name of the sheet that was defined as active */
protected $activeSheetName;

/**
* @param string $filePath Path of the file to be read
* @param \Box\Spout\Reader\ODS\ReaderOptions $options Reader's current options
Expand All @@ -52,6 +56,9 @@ public function __construct($filePath, $options)

/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
$this->escaper = \Box\Spout\Common\Escaper\ODS::getInstance();

$settingsHelper = new SettingsHelper();
$this->activeSheetName = $settingsHelper->getActiveSheetName($filePath);
}

/**
Expand Down Expand Up @@ -115,8 +122,27 @@ public function current()
{
$escapedSheetName = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_TABLE_NAME);
$sheetName = $this->escaper->unescape($escapedSheetName);
$isActiveSheet = $this->isActiveSheet($sheetName, $this->currentSheetIndex, $this->activeSheetName);

return new Sheet($this->xmlReader, $this->currentSheetIndex, $sheetName, $isActiveSheet, $this->options);
}

return new Sheet($this->xmlReader, $this->currentSheetIndex, $sheetName, $this->options);
/**
* Returns whether the current sheet was defined as the active one
*
* @param string $sheetName Name of the current sheet
* @param int $sheetIndex Index of the current sheet
* @param string|null 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)
{
// 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.
return (
($activeSheetName === null && $sheetIndex === 0) ||
($activeSheetName === $sheetName)
);
}

/**
Expand Down
21 changes: 4 additions & 17 deletions src/Spout/Reader/Wrapper/XMLReader.php
Expand Up @@ -28,13 +28,10 @@ public function openFileInZip($zipFilePath, $fileInsideZipPath)
$wasOpenSuccessful = false;
$realPathURI = $this->getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath);

// HHVM does not check if file exists within zip file
// @link https://github.com/facebook/hhvm/issues/5779
if ($this->isRunningHHVM()) {
if ($this->fileExistsWithinZip($realPathURI)) {
$wasOpenSuccessful = $this->open($realPathURI, null, LIBXML_NONET);
}
} else {
// We need to check first that the file we are trying to read really exist because:
// - PHP emits a warning when trying to open a file that does not exist.
// - HHVM does not check if file exists within zip file (@link https://github.com/facebook/hhvm/issues/5779)
if ($this->fileExistsWithinZip($realPathURI)) {
$wasOpenSuccessful = $this->open($realPathURI, null, LIBXML_NONET);
}

Expand All @@ -54,16 +51,6 @@ public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath)
return (self::ZIP_WRAPPER . realpath($zipFilePath) . '#' . $fileInsideZipPath);
}

/**
* Returns whether the current environment is HHVM
*
* @return bool TRUE if running on HHVM, FALSE otherwise
*/
protected function isRunningHHVM()
{
return defined('HHVM_VERSION');
}

/**
* Returns whether the file at the given location exists
*
Expand Down
19 changes: 15 additions & 4 deletions src/Spout/Reader/XLSX/Helper/SheetHelper.php
Expand Up @@ -53,12 +53,18 @@ public function getSheets()
{
$sheets = [];
$sheetIndex = 0;
$activeSheetIndex = 0; // By default, the first sheet is active

$xmlReader = new XMLReader();
if ($xmlReader->openFileInZip($this->filePath, self::WORKBOOK_XML_FILE_PATH)) {
while ($xmlReader->read()) {
if ($xmlReader->isPositionedOnStartingNode('sheet')) {
$sheets[] = $this->getSheetFromSheetXMLNode($xmlReader, $sheetIndex);
if ($xmlReader->isPositionedOnStartingNode('workbookView')) {
// The "workbookView" node is located before "sheet" nodes, ensuring that
// the active sheet is known before parsing sheets data.
$activeSheetIndex = (int) $xmlReader->getAttribute('activeTab');
} else if ($xmlReader->isPositionedOnStartingNode('sheet')) {
$isSheetActive = ($sheetIndex === $activeSheetIndex);
$sheets[] = $this->getSheetFromSheetXMLNode($xmlReader, $sheetIndex, $isSheetActive);
$sheetIndex++;
} else if ($xmlReader->isPositionedOnEndingNode('sheets')) {
// stop reading once all sheets have been read
Expand All @@ -79,9 +85,10 @@ public function getSheets()
*
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReaderOnSheetNode XML Reader instance, pointing on the node describing the sheet, as defined in "workbook.xml"
* @param int $sheetIndexZeroBased Index of the sheet, based on order of appearance in the workbook (zero-based)
* @param bool $isSheetActive Whether this sheet was defined as active
* @return \Box\Spout\Reader\XLSX\Sheet Sheet instance
*/
protected function getSheetFromSheetXMLNode($xmlReaderOnSheetNode, $sheetIndexZeroBased)
protected function getSheetFromSheetXMLNode($xmlReaderOnSheetNode, $sheetIndexZeroBased, $isSheetActive)
{
$sheetId = $xmlReaderOnSheetNode->getAttribute('r:id');
$escapedSheetName = $xmlReaderOnSheetNode->getAttribute('name');
Expand All @@ -92,7 +99,11 @@ protected function getSheetFromSheetXMLNode($xmlReaderOnSheetNode, $sheetIndexZe

$sheetDataXMLFilePath = $this->getSheetDataXMLFilePathForSheetId($sheetId);

return new Sheet($this->filePath, $sheetDataXMLFilePath, $sheetIndexZeroBased, $sheetName, $this->options, $this->sharedStringsHelper);
return new Sheet(
$this->filePath, $sheetDataXMLFilePath,
$sheetIndexZeroBased, $sheetName, $isSheetActive,
$this->options, $this->sharedStringsHelper
);
}

/**
Expand Down
16 changes: 15 additions & 1 deletion src/Spout/Reader/XLSX/Sheet.php
Expand Up @@ -21,19 +21,24 @@ class Sheet implements SheetInterface
/** @var string Name of the sheet */
protected $name;

/** @var bool Whether the sheet was the active one */
protected $isActive;

/**
* @param string $filePath Path of the XLSX file being read
* @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
* @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 \Box\Spout\Reader\XLSX\ReaderOptions $options Reader's current options
* @param Helper\SharedStringsHelper Helper to work with shared strings
*/
public function __construct($filePath, $sheetDataXMLFilePath, $sheetIndex, $sheetName, $options, $sharedStringsHelper)
public function __construct($filePath, $sheetDataXMLFilePath, $sheetIndex, $sheetName, $isSheetActive, $options, $sharedStringsHelper)
{
$this->rowIterator = new RowIterator($filePath, $sheetDataXMLFilePath, $options, $sharedStringsHelper);
$this->index = $sheetIndex;
$this->name = $sheetName;
$this->isActive = $isSheetActive;
}

/**
Expand Down Expand Up @@ -62,4 +67,13 @@ public function getName()
{
return $this->name;
}

/**
* @api
* @return bool Whether the sheet was defined as active
*/
public function isActive()
{
return $this->isActive;
}
}
46 changes: 46 additions & 0 deletions tests/Spout/Reader/CSV/SheetTest.php
@@ -0,0 +1,46 @@
<?php

namespace Box\Spout\Reader\CSV;

use Box\Spout\Common\Type;
use Box\Spout\Reader\ReaderFactory;
use Box\Spout\TestUsingResource;

/**
* Class SheetTest
*
* @package Box\Spout\Reader\CSV
*/
class SheetTest extends \PHPUnit_Framework_TestCase
{
use TestUsingResource;

/**
* @return void
*/
public function testReaderShouldReturnCorrectSheetInfos()
{
$sheet = $this->openFileAndReturnSheet('csv_standard.csv');

$this->assertEquals('', $sheet->getName());
$this->assertEquals(0, $sheet->getIndex());
$this->assertTrue($sheet->isActive());
}

/**
* @param string $fileName
* @return Sheet
*/
private function openFileAndReturnSheet($fileName)
{
$resourcePath = $this->getResourcePath($fileName);
$reader = ReaderFactory::create(Type::CSV);
$reader->open($resourcePath);

$sheet = $reader->getSheetIterator()->current();

$reader->close();

return $sheet;
}
}
17 changes: 16 additions & 1 deletion tests/Spout/Reader/ODS/SheetTest.php
Expand Up @@ -18,15 +18,30 @@ class SheetTest extends \PHPUnit_Framework_TestCase
/**
* @return void
*/
public function testNextSheetShouldReturnCorrectSheetInfos()
public function testReaderShouldReturnCorrectSheetInfos()
{
// NOTE: This spreadsheet has its second tab defined as active
$sheets = $this->openFileAndReturnSheets('two_sheets_with_custom_names.ods');

$this->assertEquals('Sheet First', $sheets[0]->getName());
$this->assertEquals(0, $sheets[0]->getIndex());
$this->assertFalse($sheets[0]->isActive());

$this->assertEquals('Sheet Last', $sheets[1]->getName());
$this->assertEquals(1, $sheets[1]->getIndex());
$this->assertTrue($sheets[1]->isActive());
}

/**
* @return void
*/
public function testReaderShouldDefineFirstSheetAsActiveByDefault()
{
// NOTE: This spreadsheet has no information about the active sheet
$sheets = $this->openFileAndReturnSheets('two_sheets_with_no_settings_xml_file.ods');

$this->assertTrue($sheets[0]->isActive());
$this->assertFalse($sheets[1]->isActive());
}

/**
Expand Down

0 comments on commit 7f8b95b

Please sign in to comment.