Add support for 1904 dates
This commit adds support for dates using the 1904 calendar (starting 1904-01-01 00:00:00). It also fixes some issues with the dates in 1900 calendar (which now correctly start at 1899-12-30 00:00:00). Finally, it is now possible to have negative timestamps, representing dates before the base date (and up to 0000-01-01 00:00:00), as per the SpreadsheetML specs. Note that some versions of Excel don't support negative dates...
This commit is contained in:
1 parent
e1ae3c8a81
commit
c74c0d9127
9 files changed
+194
-102
No files matched your search
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Box\Spout\Reader\XLSX\Manager;
|
||||
|
||||
use Box\Spout\Reader\Common\Entity\Options;
|
||||
use Box\Spout\Reader\Common\XMLProcessor;
|
||||
use Box\Spout\Reader\XLSX\Creator\EntityFactory;
|
||||
use Box\Spout\Reader\XLSX\Sheet;
|
||||
|
||||
@@ -16,12 +18,14 @@ class SheetManager
|
||||
const WORKBOOK_XML_FILE_PATH = 'xl/workbook.xml';
|
||||
|
||||
/** Definition of XML node names used to parse data */
|
||||
const XML_NODE_WORKBOOK_PROPERTIES = 'workbookPr';
|
||||
const XML_NODE_WORKBOOK_VIEW = 'workbookView';
|
||||
const XML_NODE_SHEET = 'sheet';
|
||||
const XML_NODE_SHEETS = 'sheets';
|
||||
const XML_NODE_RELATIONSHIP = 'Relationship';
|
||||
|
||||
/** Definition of XML attributes used to parse data */
|
||||
const XML_ATTRIBUTE_DATE_1904 = 'date1904';
|
||||
const XML_ATTRIBUTE_ACTIVE_TAB = 'activeTab';
|
||||
const XML_ATTRIBUTE_R_ID = 'r:id';
|
||||
const XML_ATTRIBUTE_NAME = 'name';
|
||||
@@ -46,6 +50,15 @@ class SheetManager
|
||||
/** @var \Box\Spout\Common\Helper\Escaper\XLSX Used to unescape XML data */
|
||||
protected $escaper;
|
||||
|
||||
/** @var array List of sheets */
|
||||
protected $sheets;
|
||||
|
||||
/** @var int Index of the sheet currently read */
|
||||
protected $currentSheetIndex;
|
||||
|
||||
/** @var int Index of the active sheet (0 by default) */
|
||||
protected $activeSheetIndex;
|
||||
|
||||
/**
|
||||
* @param string $filePath Path of the XLSX file being read
|
||||
* @param \Box\Spout\Common\Manager\OptionsManagerInterface $optionsManager Reader's options manager
|
||||
@@ -71,32 +84,70 @@ class SheetManager
|
||||
*/
|
||||
public function getSheets()
|
||||
{
|
||||
$sheets = [];
|
||||
$sheetIndex = 0;
|
||||
$activeSheetIndex = 0; // By default, the first sheet is active
|
||||
$this->sheets = [];
|
||||
$this->currentSheetIndex = 0;
|
||||
$this->activeSheetIndex = 0; // By default, the first sheet is active
|
||||
|
||||
$xmlReader = $this->entityFactory->createXMLReader();
|
||||
$xmlProcessor = $this->entityFactory->createXMLProcessor($xmlReader);
|
||||
|
||||
$xmlProcessor->registerCallback(self::XML_NODE_WORKBOOK_PROPERTIES, XMLProcessor::NODE_TYPE_START, [$this, 'processWorkbookPropertiesStartingNode']);
|
||||
$xmlProcessor->registerCallback(self::XML_NODE_WORKBOOK_VIEW, XMLProcessor::NODE_TYPE_START, [$this, 'processWorkbookViewStartingNode']);
|
||||
$xmlProcessor->registerCallback(self::XML_NODE_SHEET, XMLProcessor::NODE_TYPE_START, [$this, 'processSheetStartingNode']);
|
||||
$xmlProcessor->registerCallback(self::XML_NODE_SHEETS, XMLProcessor::NODE_TYPE_END, [$this, 'processSheetsEndingNode']);
|
||||
|
||||
if ($xmlReader->openFileInZip($this->filePath, self::WORKBOOK_XML_FILE_PATH)) {
|
||||
while ($xmlReader->read()) {
|
||||
if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_WORKBOOK_VIEW)) {
|
||||
// The "workbookView" node is located before "sheet" nodes, ensuring that
|
||||
// the active sheet is known before parsing sheets data.
|
||||
$activeSheetIndex = (int) $xmlReader->getAttribute(self::XML_ATTRIBUTE_ACTIVE_TAB);
|
||||
} elseif ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_SHEET)) {
|
||||
$isSheetActive = ($sheetIndex === $activeSheetIndex);
|
||||
$sheets[] = $this->getSheetFromSheetXMLNode($xmlReader, $sheetIndex, $isSheetActive);
|
||||
$sheetIndex++;
|
||||
} elseif ($xmlReader->isPositionedOnEndingNode(self::XML_NODE_SHEETS)) {
|
||||
// stop reading once all sheets have been read
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$xmlProcessor->readUntilStopped();
|
||||
$xmlReader->close();
|
||||
}
|
||||
|
||||
return $sheets;
|
||||
return $this->sheets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<workbookPr>" starting node
|
||||
* @return int A return code that indicates what action should the processor take next
|
||||
*/
|
||||
protected function processWorkbookPropertiesStartingNode($xmlReader)
|
||||
{
|
||||
$shouldUse1904Dates = (bool) $xmlReader->getAttribute(self::XML_ATTRIBUTE_DATE_1904);
|
||||
$this->optionsManager->setOption(Options::SHOULD_USE_1904_DATES, $shouldUse1904Dates);
|
||||
|
||||
return XMLProcessor::PROCESSING_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<workbookView>" starting node
|
||||
* @return int A return code that indicates what action should the processor take next
|
||||
*/
|
||||
protected function processWorkbookViewStartingNode($xmlReader)
|
||||
{
|
||||
// The "workbookView" node is located before "sheet" nodes, ensuring that
|
||||
// the active sheet is known before parsing sheets data.
|
||||
$this->activeSheetIndex = (int) $xmlReader->getAttribute(self::XML_ATTRIBUTE_ACTIVE_TAB);
|
||||
|
||||
return XMLProcessor::PROCESSING_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<sheet>" starting node
|
||||
* @return int A return code that indicates what action should the processor take next
|
||||
*/
|
||||
protected function processSheetStartingNode($xmlReader)
|
||||
{
|
||||
$isSheetActive = ($this->currentSheetIndex === $this->activeSheetIndex);
|
||||
$this->sheets[] = $this->getSheetFromSheetXMLNode($xmlReader, $this->currentSheetIndex, $isSheetActive);
|
||||
$this->currentSheetIndex++;
|
||||
|
||||
return XMLProcessor::PROCESSING_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int A return code that indicates what action should the processor take next
|
||||
*/
|
||||
protected function processSheetsEndingNode()
|
||||
{
|
||||
return XMLProcessor::PROCESSING_STOP;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user