Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
209372462b | ||
|
|
4a5da2ad74 | ||
|
|
c48c07db99 | ||
|
|
a804be4844 | ||
|
|
4bfbb41c95 | ||
|
|
61b71bf379 |
No files matched your search
+1
-1
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4.x-dev"
|
||||
"dev-master": "2.5.x-dev"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ class CellValueFormatter
|
||||
|
||||
/**
|
||||
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
|
||||
* @TODO Add other types !!
|
||||
* @see http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#refTable13
|
||||
*
|
||||
* @param \DOMNode $node
|
||||
@@ -119,7 +118,8 @@ class CellValueFormatter
|
||||
protected function formatFloatCellValue($node)
|
||||
{
|
||||
$nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_VALUE);
|
||||
$cellValue = is_int($nodeValue) ? intval($nodeValue) : floatval($nodeValue);
|
||||
$nodeIntValue = intval($nodeValue);
|
||||
$cellValue = ($nodeIntValue == $nodeValue) ? $nodeIntValue : floatval($nodeValue);
|
||||
return $cellValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class Sheet implements SheetInterface
|
||||
/** @var int ID of the sheet */
|
||||
protected $id;
|
||||
|
||||
/** @var int Index of the sheet, based on order of creation (zero-based) */
|
||||
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
|
||||
protected $index;
|
||||
|
||||
/** @var string Name of the sheet */
|
||||
@@ -27,7 +27,7 @@ class Sheet implements SheetInterface
|
||||
|
||||
/**
|
||||
* @param XMLReader $xmlReader XML Reader, positioned on the "<table:table>" element
|
||||
* @param int $sheetIndex Index of the sheet, based on order of creation (zero-based)
|
||||
* @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
|
||||
* @param string $sheetName Name of the sheet
|
||||
*/
|
||||
public function __construct($xmlReader, $sheetIndex, $sheetName)
|
||||
@@ -48,7 +48,7 @@ class Sheet implements SheetInterface
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @return int Index of the sheet, based on order of creation (zero-based)
|
||||
* @return int Index of the sheet, based on order in the workbook (zero-based)
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
|
||||
@@ -169,7 +169,8 @@ class CellValueFormatter
|
||||
if ($shouldFormatAsDate) {
|
||||
return $this->formatExcelTimestampValue(floatval($nodeValue));
|
||||
} else {
|
||||
return is_int($nodeValue) ? intval($nodeValue) : floatval($nodeValue);
|
||||
$nodeIntValue = intval($nodeValue);
|
||||
return ($nodeIntValue == $nodeValue) ? $nodeIntValue : floatval($nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,6 @@ use Box\Spout\Reader\XLSX\Sheet;
|
||||
*/
|
||||
class SheetHelper
|
||||
{
|
||||
/** Extension for XML files */
|
||||
const XML_EXTENSION = '.xml';
|
||||
|
||||
/** Paths of XML files relative to the XLSX file root */
|
||||
const CONTENT_TYPES_XML_FILE_PATH = '[Content_Types].xml';
|
||||
const WORKBOOK_XML_RELS_FILE_PATH = 'xl/_rels/workbook.xml.rels';
|
||||
@@ -79,9 +76,15 @@ class SheetHelper
|
||||
$sheetNode = $sheetNodes[$i];
|
||||
$sheetDataXMLFilePath = $sheetNode->getAttribute('PartName');
|
||||
|
||||
$sheets[] = $this->getSheetFromXML($sheetDataXMLFilePath, $i);
|
||||
$sheets[] = $this->getSheetFromXML($sheetDataXMLFilePath);
|
||||
}
|
||||
|
||||
// make sure the sheets are sorted by index
|
||||
// (as the sheets are not necessarily in this order in the XML file)
|
||||
usort($sheets, function ($sheet1, $sheet2) {
|
||||
return ($sheet1->getIndex() - $sheet2->getIndex());
|
||||
});
|
||||
|
||||
return $sheets;
|
||||
}
|
||||
|
||||
@@ -91,60 +94,37 @@ class SheetHelper
|
||||
* Then we look at "xl/worbook.xml" to find the sheet entry associated to the found ID.
|
||||
* The entry contains the ID and name of the sheet.
|
||||
*
|
||||
* If this piece of data can't be found by parsing the different XML files, the ID will default
|
||||
* to the sheet index, based on order in [Content_Types].xml. Similarly, the sheet's name will
|
||||
* default to the data sheet XML file name ("xl/worksheets/sheet2.xml" => "sheet2").
|
||||
*
|
||||
* @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
|
||||
* @param int $sheetIndexZeroBased Index of the sheet, based on order in [Content_Types].xml (zero-based)
|
||||
* @return \Box\Spout\Reader\XLSX\Sheet Sheet instance
|
||||
*/
|
||||
protected function getSheetFromXML($sheetDataXMLFilePath, $sheetIndexZeroBased)
|
||||
protected function getSheetFromXML($sheetDataXMLFilePath)
|
||||
{
|
||||
$sheetName = $this->getDefaultSheetName($sheetDataXMLFilePath);
|
||||
|
||||
/*
|
||||
* In [Content_Types].xml, the path is "/xl/worksheets/sheet1.xml"
|
||||
* In workbook.xml.rels, it is only "worksheets/sheet1.xml"
|
||||
*/
|
||||
// In [Content_Types].xml, the path is "/xl/worksheets/sheet1.xml"
|
||||
// In workbook.xml.rels, it is only "worksheets/sheet1.xml"
|
||||
$sheetDataXMLFilePathInWorkbookXMLRels = ltrim($sheetDataXMLFilePath, '/xl/');
|
||||
|
||||
// find the node associated to the given file path
|
||||
$workbookXMLResElement = $this->getWorkbookXMLRelsAsXMLElement();
|
||||
$relationshipNodes = $workbookXMLResElement->xpath('//ns:Relationship[@Target="' . $sheetDataXMLFilePathInWorkbookXMLRels . '"]');
|
||||
$relationshipNode = $relationshipNodes[0];
|
||||
|
||||
if (count($relationshipNodes) === 1) {
|
||||
$relationshipNode = $relationshipNodes[0];
|
||||
$sheetId = $relationshipNode->getAttribute('Id');
|
||||
$relationshipSheetId = $relationshipNode->getAttribute('Id');
|
||||
|
||||
$workbookXMLElement = $this->getWorkbookXMLAsXMLElement();
|
||||
$sheetNodes = $workbookXMLElement->xpath('//ns:sheet[@r:id="' . $sheetId . '"]');
|
||||
$workbookXMLElement = $this->getWorkbookXMLAsXMLElement();
|
||||
$sheetNodes = $workbookXMLElement->xpath('//ns:sheet[@r:id="' . $relationshipSheetId . '"]');
|
||||
$sheetNode = $sheetNodes[0];
|
||||
|
||||
if (count($sheetNodes) === 1) {
|
||||
$sheetNode = $sheetNodes[0];
|
||||
$escapedSheetName = $sheetNode->getAttribute('name');
|
||||
$escapedSheetName = $sheetNode->getAttribute('name');
|
||||
$sheetIdOneBased = $sheetNode->getAttribute('sheetId');
|
||||
$sheetIndexZeroBased = $sheetIdOneBased - 1;
|
||||
|
||||
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
||||
$escaper = new \Box\Spout\Common\Escaper\XLSX();
|
||||
$sheetName = $escaper->unescape($escapedSheetName);
|
||||
}
|
||||
}
|
||||
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
||||
$escaper = new \Box\Spout\Common\Escaper\XLSX();
|
||||
$sheetName = $escaper->unescape($escapedSheetName);
|
||||
|
||||
return new Sheet($this->filePath, $sheetDataXMLFilePath, $this->sharedStringsHelper, $sheetIndexZeroBased, $sheetName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default name of the sheet whose data is located
|
||||
* at the given path.
|
||||
*
|
||||
* @param string $sheetDataXMLFilePath Path of the sheet data XML file
|
||||
* @return string The default sheet name
|
||||
*/
|
||||
protected function getDefaultSheetName($sheetDataXMLFilePath)
|
||||
{
|
||||
return $this->globalFunctionsHelper->basename($sheetDataXMLFilePath, self::XML_EXTENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a representation of the workbook.xml.rels file, ready to be parsed.
|
||||
* The returned value is cached.
|
||||
|
||||
@@ -15,7 +15,7 @@ class Sheet implements SheetInterface
|
||||
/** @var \Box\Spout\Reader\XLSX\RowIterator To iterate over sheet's rows */
|
||||
protected $rowIterator;
|
||||
|
||||
/** @var int Index of the sheet, based on order of creation (zero-based) */
|
||||
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
|
||||
protected $index;
|
||||
|
||||
/** @var string Name of the sheet */
|
||||
@@ -25,7 +25,7 @@ class Sheet implements SheetInterface
|
||||
* @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 Helper\SharedStringsHelper Helper to work with shared strings
|
||||
* @param int $sheetIndex Index of the sheet, based on order of creation (zero-based)
|
||||
* @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
|
||||
* @param string $sheetName Name of the sheet
|
||||
*/
|
||||
public function __construct($filePath, $sheetDataXMLFilePath, $sharedStringsHelper, $sheetIndex, $sheetName)
|
||||
@@ -46,7 +46,7 @@ class Sheet implements SheetInterface
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @return int Index of the sheet, based on order of creation (zero-based)
|
||||
* @return int Index of the sheet, based on order in the workbook (zero-based)
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ class Sheet
|
||||
/** @var array Associative array [SHEET_INDEX] => [SHEET_NAME] keeping track of sheets' name to enforce uniqueness */
|
||||
protected static $SHEETS_NAME_USED = [];
|
||||
|
||||
/** @var int Index of the sheet, based on order of creation (zero-based) */
|
||||
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
|
||||
protected $index;
|
||||
|
||||
/** @var string Name of the sheet */
|
||||
@@ -34,7 +34,7 @@ class Sheet
|
||||
protected $stringHelper;
|
||||
|
||||
/**
|
||||
* @param int $sheetIndex Index of the sheet, based on order of creation (zero-based)
|
||||
* @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
|
||||
*/
|
||||
public function __construct($sheetIndex)
|
||||
{
|
||||
@@ -45,7 +45,7 @@ class Sheet
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @return int Index of the sheet, based on order of creation (zero-based)
|
||||
* @return int Index of the sheet, based on order in the workbook (zero-based)
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
|
||||
@@ -12,23 +12,26 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function dataProviderForExcelDateTest()
|
||||
public function dataProviderForTestExcelDate()
|
||||
{
|
||||
return [
|
||||
[ CellValueFormatter::CELL_TYPE_NUMERIC, 42429, '2016-02-29 00:00:00' ],
|
||||
[ CellValueFormatter::CELL_TYPE_NUMERIC, '146098', '2299-12-31 00:00:00' ],
|
||||
[ CellValueFormatter::CELL_TYPE_NUMERIC, -700, null ],
|
||||
[ CellValueFormatter::CELL_TYPE_NUMERIC, 0, null ],
|
||||
[ CellValueFormatter::CELL_TYPE_NUMERIC, 0.5, null ],
|
||||
[ CellValueFormatter::CELL_TYPE_NUMERIC, 1, '1900-01-01 00:00:00' ],
|
||||
[ CellValueFormatter::CELL_TYPE_NUMERIC, 59.999988425926, '1900-02-28 23:59:59' ],
|
||||
[ CellValueFormatter::CELL_TYPE_NUMERIC, 60.458333333333, '1900-02-28 11:00:00' ],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 42429, '2016-02-29 00:00:00'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, '146098', '2299-12-31 00:00:00'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, -700, null],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 0, null],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 0.5, null],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 1, '1900-01-01 00:00:00'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 59.999988425926, '1900-02-28 23:59:59'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 60.458333333333, '1900-02-28 11:00:00'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderForExcelDateTest
|
||||
* @dataProvider dataProviderForTestExcelDate
|
||||
*
|
||||
* @param string $cellType
|
||||
* @param int|float|string $nodeValue
|
||||
* @param string|null $expectedDateAsString
|
||||
* @return void
|
||||
*/
|
||||
public function testExcelDate($cellType, $nodeValue, $expectedDateAsString)
|
||||
@@ -39,7 +42,7 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
->expects($this->atLeastOnce())
|
||||
->method('item')
|
||||
->with(0)
|
||||
->will($this->returnValue((object)[ 'nodeValue' => $nodeValue ]));
|
||||
->will($this->returnValue((object)['nodeValue' => $nodeValue]));
|
||||
|
||||
$nodeMock = $this->getMockBuilder('DOMElement')->disableOriginalConstructor()->getMock();
|
||||
|
||||
@@ -47,8 +50,8 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
->expects($this->atLeastOnce())
|
||||
->method('getAttribute')
|
||||
->will($this->returnValueMap([
|
||||
[ CellValueFormatter::XML_ATTRIBUTE_TYPE, $cellType ],
|
||||
[ CellValueFormatter::XML_ATTRIBUTE_STYLE_ID, 123 ],
|
||||
[CellValueFormatter::XML_ATTRIBUTE_TYPE, $cellType],
|
||||
[CellValueFormatter::XML_ATTRIBUTE_STYLE_ID, 123],
|
||||
]));
|
||||
|
||||
$nodeMock
|
||||
@@ -57,7 +60,7 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
->with(CellValueFormatter::XML_NODE_VALUE)
|
||||
->will($this->returnValue($nodeListMock));
|
||||
|
||||
$styleHelperMock = $this->getMockBuilder(__NAMESPACE__ . '\StyleHelper')->disableOriginalConstructor()->getMock();
|
||||
$styleHelperMock = $this->getMockBuilder('Box\Spout\Reader\XLSX\Helper\StyleHelper')->disableOriginalConstructor()->getMock();
|
||||
|
||||
$styleHelperMock
|
||||
->expects($this->once())
|
||||
@@ -65,9 +68,8 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
->with(123)
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$instance = new CellValueFormatter(null, $styleHelperMock);
|
||||
|
||||
$result = $instance->extractAndFormatNodeValue($nodeMock);
|
||||
$formatter = new CellValueFormatter(null, $styleHelperMock);
|
||||
$result = $formatter->extractAndFormatNodeValue($nodeMock);
|
||||
|
||||
if ($expectedDateAsString === null) {
|
||||
$this->assertNull($result);
|
||||
@@ -77,4 +79,48 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function dataProviderForTestFormatNumericCellValueWithNumbers()
|
||||
{
|
||||
return [
|
||||
[42, 42, 'integer'],
|
||||
[42.5, 42.5, 'double'],
|
||||
[-42, -42, 'integer'],
|
||||
[-42.5, -42.5, 'double'],
|
||||
['42', 42, 'integer'],
|
||||
['42.5', 42.5, 'double'],
|
||||
[865640023012945, 865640023012945, 'integer'],
|
||||
['865640023012945', 865640023012945, 'integer'],
|
||||
[865640023012945.5, 865640023012945.5, 'double'],
|
||||
['865640023012945.5', 865640023012945.5, 'double'],
|
||||
[PHP_INT_MAX, PHP_INT_MAX, 'integer'],
|
||||
[~PHP_INT_MAX + 1, ~PHP_INT_MAX + 1, 'integer'], // ~PHP_INT_MAX === PHP_INT_MIN, PHP_INT_MIN being PHP7+
|
||||
[PHP_INT_MAX + 1, PHP_INT_MAX + 1, 'double'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderForTestFormatNumericCellValueWithNumbers
|
||||
*
|
||||
* @param int|float|string $value
|
||||
* @param int|float $expectedFormattedValue
|
||||
* @param string $expectedType
|
||||
* @return void
|
||||
*/
|
||||
public function testFormatNumericCellValueWithNumbers($value, $expectedFormattedValue, $expectedType)
|
||||
{
|
||||
$styleHelperMock = $this->getMockBuilder('Box\Spout\Reader\XLSX\Helper\StyleHelper')->disableOriginalConstructor()->getMock();
|
||||
$styleHelperMock
|
||||
->expects($this->once())
|
||||
->method('shouldFormatNumericValueAsDate')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$formatter = new CellValueFormatter(null, $styleHelperMock);
|
||||
$formattedValue = \ReflectionHelper::callMethodOnObject($formatter, 'formatNumericCellValue', $value, 0);
|
||||
|
||||
$this->assertEquals($expectedFormattedValue, $formattedValue);
|
||||
$this->assertEquals($expectedType, gettype($formattedValue));
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,28 @@ class ReaderTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testReadShouldSupportSheetsDefinitionInRandomOrder()
|
||||
{
|
||||
$allRows = $this->getAllRowsForFile('two_sheets_with_sheets_definition_in_reverse_order.xlsx');
|
||||
|
||||
$expectedRows = [
|
||||
['s1 - A1', 's1 - B1', 's1 - C1', 's1 - D1', 's1 - E1'],
|
||||
['s1 - A2', 's1 - B2', 's1 - C2', 's1 - D2', 's1 - E2'],
|
||||
['s1 - A3', 's1 - B3', 's1 - C3', 's1 - D3', 's1 - E3'],
|
||||
['s1 - A4', 's1 - B4', 's1 - C4', 's1 - D4', 's1 - E4'],
|
||||
['s1 - A5', 's1 - B5', 's1 - C5', 's1 - D5', 's1 - E5'],
|
||||
['s2 - A1', 's2 - B1', 's2 - C1', 's2 - D1', 's2 - E1'],
|
||||
['s2 - A2', 's2 - B2', 's2 - C2', 's2 - D2', 's2 - E2'],
|
||||
['s2 - A3', 's2 - B3', 's2 - C3', 's2 - D3', 's2 - E3'],
|
||||
['s2 - A4', 's2 - B4', 's2 - C4', 's2 - D4', 's2 - E4'],
|
||||
['s2 - A5', 's2 - B5', 's2 - C5', 's2 - D5', 's2 - E5'],
|
||||
];
|
||||
$this->assertEquals($expectedRows, $allRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
||||
Binary file not shown.
Reference in new issue
Block a user