Add support for more cell types
Added proper support for booleans, dates, numbers, errors. Added unescaping of the read string. Fixed a bug when cells did not have any values => now returns empty string.
This commit is contained in:
parent
d617a50b85
commit
8bac924d48
@ -177,6 +177,7 @@ class XLSX extends AbstractReader
|
||||
throw new BadUsageException('You must call nextSheet() before calling hasNextRow() or nextRow()');
|
||||
}
|
||||
|
||||
$escaper = new \Box\Spout\Common\Escaper\XLSX();
|
||||
$isInsideRowTag = false;
|
||||
$rowData = [];
|
||||
|
||||
@ -188,6 +189,7 @@ class XLSX extends AbstractReader
|
||||
$lastCellIndex = $matches[1];
|
||||
$this->numberOfColumns = CellHelper::getColumnIndexFromCellIndex($lastCellIndex) + 1;
|
||||
}
|
||||
|
||||
} else if ($this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === 'row') {
|
||||
// Start of the row description
|
||||
$isInsideRowTag = true;
|
||||
@ -200,32 +202,15 @@ class XLSX extends AbstractReader
|
||||
$numberOfColumnsForRow = intval($numberOfColumnsForRow);
|
||||
}
|
||||
$rowData = ($numberOfColumnsForRow !== 0) ? array_fill(0, $numberOfColumnsForRow, '') : [];
|
||||
|
||||
} else if ($isInsideRowTag && $this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === 'c') {
|
||||
// Start of a cell description
|
||||
$currentCellIndex = $this->xmlReader->getAttribute('r');
|
||||
$currentColumnIndex = CellHelper::getColumnIndexFromCellIndex($currentCellIndex);
|
||||
|
||||
$node = $this->xmlReader->expand();
|
||||
$rowData[$currentColumnIndex] = $this->getCellValue($node, $escaper);
|
||||
|
||||
$hasInlineString = ($this->xmlReader->getAttribute('t') === 'inlineStr');
|
||||
$hasSharedString = ($this->xmlReader->getAttribute('t') === 's');
|
||||
|
||||
if ($hasInlineString) {
|
||||
// inline strings are formatted this way:
|
||||
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t></is></c>
|
||||
$tNode = $node->getElementsByTagName('t')->item(0);
|
||||
$rowData[$currentColumnIndex] = trim($tNode->nodeValue);
|
||||
} else if ($hasSharedString) {
|
||||
// shared strings are formatted this way:
|
||||
// <c r="A1" t="s"><v>[SHARED_STRING_INDEX]</v></c>
|
||||
$vNode = $node->getElementsByTagName('v')->item(0);
|
||||
$sharedStringIndex = intval($vNode->nodeValue);
|
||||
$rowData[$currentColumnIndex] = $this->sharedStringsHelper->getStringAtIndex($sharedStringIndex);
|
||||
} else {
|
||||
// other values are formatted this way:
|
||||
// <c r="A1"><v>[VALUE]</v></c>
|
||||
$vNode = $node->getElementsByTagName('v')->item(0);
|
||||
$rowData[$currentColumnIndex] = intval($vNode->nodeValue);
|
||||
}
|
||||
} else if ($this->xmlReader->nodeType == \XMLReader::END_ELEMENT && $this->xmlReader->name === 'row') {
|
||||
// End of the row description
|
||||
// If needed, we fill the empty cells
|
||||
@ -238,6 +223,58 @@ class XLSX extends AbstractReader
|
||||
return ($rowData !== []) ? $rowData : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the (unescaped) cell value associated to the given XML node.
|
||||
*
|
||||
* @param \DOMNode $node
|
||||
* @param \Box\Spout\Common\Escaper\XLSX $escaper
|
||||
* @return string|int|float|bool|null The value associated with the cell (null when the cell has an error)
|
||||
*/
|
||||
protected function getCellValue($node, $escaper)
|
||||
{
|
||||
$cellValue = '';
|
||||
|
||||
// Default cell type is "n"
|
||||
$cellType = $node->getAttribute('t') ?: 'n';
|
||||
|
||||
if ($cellType === 'inlineStr') {
|
||||
// inline strings are formatted this way:
|
||||
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t></is></c>
|
||||
$tNode = $node->getElementsByTagName('t')->item(0);
|
||||
$escapedCellValue = trim($tNode->nodeValue);
|
||||
$cellValue = $escaper->unescape($escapedCellValue);
|
||||
} else {
|
||||
// all other cell types should have a "v" tag containing the value.
|
||||
// if not, the returned value should be empty string.
|
||||
$vNode = $node->getElementsByTagName('v')->item(0);
|
||||
|
||||
if ($vNode !== null) {
|
||||
if ($cellType === 's') {
|
||||
// shared strings are formatted this way:
|
||||
// <c r="A1" t="s"><v>[SHARED_STRING_INDEX]</v></c>
|
||||
$sharedStringIndex = intval($vNode->nodeValue);
|
||||
$escapedCellValue = $this->sharedStringsHelper->getStringAtIndex($sharedStringIndex);
|
||||
$cellValue = $escaper->unescape($escapedCellValue);
|
||||
} else if ($cellType === 'b') {
|
||||
// !! is similar to boolval()
|
||||
$cellValue = !!$vNode->nodeValue;
|
||||
} else if ($cellType === 'n') {
|
||||
$nodeValue = $vNode->nodeValue;
|
||||
$cellValue = is_int($nodeValue) ? intval($nodeValue) : floatval($nodeValue);
|
||||
} else if ($cellType === 'd') {
|
||||
$cellValue = new \DateTime($vNode->nodeValue);
|
||||
} else if ($cellType === 'e') {
|
||||
$cellValue = null;
|
||||
} else if ($cellType === 'str') {
|
||||
$escapedCellValue = trim($vNode->nodeValue);
|
||||
$cellValue = $escaper->unescape($escapedCellValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cellValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the reader. To be used after reading the file.
|
||||
*
|
||||
|
@ -114,6 +114,27 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals($expectedRows, $allRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testReadShouldSupportAllCellTypes()
|
||||
{
|
||||
$allRows = $this->getAllRowsForFile('sheet_with_all_cell_types.xlsx');
|
||||
|
||||
$expectedRows = [
|
||||
[
|
||||
's1--A1', 's1--A2',
|
||||
false, true,
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '2015-06-03 13:21:58'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '2015-06-01 00:00:00'),
|
||||
10, 10.43,
|
||||
null,
|
||||
],
|
||||
['', '', '', '', '', '', '', '', ''],
|
||||
];
|
||||
$this->assertEquals($expectedRows, $allRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
BIN
tests/resources/xlsx/sheet_with_all_cell_types.xlsx
Normal file
BIN
tests/resources/xlsx/sheet_with_all_cell_types.xlsx
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user