Xlsx reader do not read rows and columns filtered out in readFilter at all

Set rows and columns dimensions for only cells rows and columns
allowed by readfilter

Fixes 
Closes 
This commit is contained in:
Dominik 2018-03-15 11:29:18 +01:00 committed by Adrien Crivelli
parent 7e9f43bf5b
commit b509b672e0
No known key found for this signature in database
GPG Key ID: B182FD79DC6DE92E
4 changed files with 251 additions and 44 deletions
CHANGELOG.md
src/PhpSpreadsheet/Reader
tests/PhpSpreadsheetTests/Functional

View File

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Subtotal 9 in a group that has other subtotals 9 exclude the totals of the other subtotals in the range - [#332](https://github.com/PHPOffice/PhpSpreadsheet/issues/332)
- `Helper\Html` support UTF-8 HTML input - [#444](https://github.com/PHPOffice/PhpSpreadsheet/issues/444)
- Xlsx loaded an extra empty comment for each real comment - [#375](https://github.com/PHPOffice/PhpSpreadsheet/issues/375)
- Xlsx reader do not read rows and columns filtered out in readFilter at all - [#370](https://github.com/PHPOffice/PhpSpreadsheet/issues/370)
## [1.2.1] - 2018-04-10

View File

@ -7,9 +7,9 @@ interface IReadFilter
/**
* Should this cell be read?
*
* @param $column string Column address (as a string value like "A", or "IV")
* @param $row int Row number
* @param $worksheetName string Optional worksheet name
* @param string $column Column address (as a string value like "A", or "IV")
* @param int $row Row number
* @param string $worksheetName Optional worksheet name
*
* @return bool
*/

View File

@ -320,6 +320,60 @@ class Xlsx extends BaseReader
return $contents;
}
/**
* Set Worksheet column attributes by attributes array passed.
*
* @param Worksheet $docSheet
* @param string $column A, B, ... DX, ...
* @param array $columnAttributes array of attributes (indexes are attribute name, values are value)
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ?
*/
private function setColumnAttributes(Worksheet $docSheet, $column, array $columnAttributes)
{
if (isset($columnAttributes['xfIndex'])) {
$docSheet->getColumnDimension($column)->setXfIndex($columnAttributes['xfIndex']);
}
if (isset($columnAttributes['visible'])) {
$docSheet->getColumnDimension($column)->setVisible($columnAttributes['visible']);
}
if (isset($columnAttributes['collapsed'])) {
$docSheet->getColumnDimension($column)->setCollapsed($columnAttributes['collapsed']);
}
if (isset($columnAttributes['outlineLevel'])) {
$docSheet->getColumnDimension($column)->setOutlineLevel($columnAttributes['outlineLevel']);
}
if (isset($columnAttributes['width'])) {
$docSheet->getColumnDimension($column)->setWidth($columnAttributes['width']);
}
}
/**
* Set Worksheet row attributes by attributes array passed.
*
* @param Worksheet $docSheet
* @param int $row 1, 2, 3, ... 99, ...
* @param array $rowAttributes array of attributes (indexes are attribute name, values are value)
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ?
*/
private function setRowAttributes(Worksheet $docSheet, $row, array $rowAttributes)
{
if (isset($rowAttributes['xfIndex'])) {
$docSheet->getRowDimension($row)->setXfIndex($rowAttributes['xfIndex']);
}
if (isset($rowAttributes['visible'])) {
$docSheet->getRowDimension($row)->setVisible($rowAttributes['visible']);
}
if (isset($rowAttributes['collapsed'])) {
$docSheet->getRowDimension($row)->setCollapsed($rowAttributes['collapsed']);
}
if (isset($rowAttributes['outlineLevel'])) {
$docSheet->getRowDimension($row)->setOutlineLevel($rowAttributes['outlineLevel']);
}
if (isset($rowAttributes['rowHeight'])) {
$docSheet->getRowDimension($row)->setRowHeight($rowAttributes['rowHeight']);
}
}
/**
* Loads Spreadsheet from file.
*
@ -819,30 +873,6 @@ class Xlsx extends BaseReader
}
}
if (isset($xmlSheet->cols) && !$this->readDataOnly) {
foreach ($xmlSheet->cols->col as $col) {
for ($i = (int) ($col['min']); $i <= (int) ($col['max']); ++$i) {
if ($col['style'] && !$this->readDataOnly) {
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setXfIndex((int) ($col['style']));
}
if (self::boolean($col['hidden'])) {
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setVisible(false);
}
if (self::boolean($col['collapsed'])) {
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setCollapsed(true);
}
if ($col['outlineLevel'] > 0) {
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setOutlineLevel((int) ($col['outlineLevel']));
}
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setWidth((float) ($col['width']));
if ((int) ($col['max']) == 16384) {
break;
}
}
}
}
if (isset($xmlSheet->printOptions) && !$this->readDataOnly) {
if (self::boolean((string) $xmlSheet->printOptions['gridLinesSet'])) {
$docSheet->setShowGridlines(true);
@ -858,25 +888,77 @@ class Xlsx extends BaseReader
}
}
$columnsAttributes = [];
$rowsAttributes = [];
if (isset($xmlSheet->cols) && !$this->readDataOnly) {
foreach ($xmlSheet->cols->col as $col) {
for ($i = (int) ($col['min']); $i <= (int) ($col['max']); ++$i) {
if ($col['style'] && !$this->readDataOnly) {
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['xfIndex'] = (int) $col['style'];
}
if (self::boolean($col['hidden'])) {
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['visible'] = false;
}
if (self::boolean($col['collapsed'])) {
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['collapsed'] = true;
}
if ($col['outlineLevel'] > 0) {
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['outlineLevel'] = (int) $col['outlineLevel'];
}
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['width'] = (float) $col['width'];
if ((int) ($col['max']) == 16384) {
break;
}
}
}
}
if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {
foreach ($xmlSheet->sheetData->row as $row) {
if ($row['ht'] && !$this->readDataOnly) {
$rowsAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
}
if (self::boolean($row['hidden']) && !$this->readDataOnly) {
$rowsAttributes[(int) $row['r']]['visible'] = false;
}
if (self::boolean($row['collapsed'])) {
$rowsAttributes[(int) $row['r']]['collapsed'] = true;
}
if ($row['outlineLevel'] > 0) {
$rowsAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
}
if ($row['s'] && !$this->readDataOnly) {
$rowsAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
}
}
}
// set columns/rows attributes
$columnsAttributesSet = [];
$rowsAttributesSet = [];
foreach ($columnsAttributes as $coordColumn => $columnAttributes) {
foreach ($rowsAttributes as $coordRow => $rowAttributes) {
if ($this->getReadFilter() !== null) {
if (!$this->getReadFilter()->readCell($coordColumn, $coordRow, $docSheet->getTitle())) {
continue;
}
}
if (!isset($columnsAttributesSet[$coordColumn])) {
$this->setColumnAttributes($docSheet, $coordColumn, $columnAttributes);
$columnsAttributesSet[$coordColumn] = true;
}
if (!isset($rowsAttributesSet[$coordRow])) {
$this->setRowAttributes($docSheet, $coordRow, $rowAttributes);
$rowsAttributesSet[$coordRow] = true;
}
}
}
if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {
$cIndex = 1; // Cell Start from 1
foreach ($xmlSheet->sheetData->row as $row) {
if ($row['ht'] && !$this->readDataOnly) {
$docSheet->getRowDimension((int) ($row['r']))->setRowHeight((float) ($row['ht']));
}
if (self::boolean($row['hidden']) && !$this->readDataOnly) {
$docSheet->getRowDimension((int) ($row['r']))->setVisible(false);
}
if (self::boolean($row['collapsed'])) {
$docSheet->getRowDimension((int) ($row['r']))->setCollapsed(true);
}
if ($row['outlineLevel'] > 0) {
$docSheet->getRowDimension((int) ($row['r']))->setOutlineLevel((int) ($row['outlineLevel']));
}
if ($row['s'] && !$this->readDataOnly) {
$docSheet->getRowDimension((int) ($row['r']))->setXfIndex((int) ($row['s']));
}
$rowIndex = 1;
foreach ($row->c as $c) {
$r = (string) $c['r'];
@ -891,7 +973,7 @@ class Xlsx extends BaseReader
if ($this->getReadFilter() !== null) {
$coordinates = Coordinate::coordinateFromString($r);
if (!$this->getReadFilter()->readCell($coordinates[0], $coordinates[1], $docSheet->getTitle())) {
if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) {
continue;
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Functional;
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
class ReadFilterTest extends AbstractFunctional
{
public function providerCellsValues()
{
$cellValues = [
// one argument as a multidimensional array
[1, 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
[2, 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
[3, 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
[4, 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
[5, 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
[6, 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
[7, 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
[8, 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
[9, 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
[10, 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
];
return [
['Xlsx', $cellValues],
['Ods', $cellValues],
];
}
/**
* Test load Xlsx file with many empty cells with no filter used.
*
* @dataProvider providerCellsValues
*
* @param array $arrayData
* @param mixed $format
*/
public function testXlsxLoadWithoutReadFilter($format, array $arrayData)
{
$spreadsheet = new Spreadsheet();
$spreadsheet->getActiveSheet()->fromArray($arrayData, null, 'A1');
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $format);
$sheet = $reloadedSpreadsheet->getSheet(0);
// test highest column (very specific num of columns because of some 3rd party software)
self::assertSame('J', $sheet->getHighestColumn());
// test highest row (very specific num of rows because of some 3rd party software)
self::assertEquals(10, $sheet->getHighestRow());
// test top left coordinate
$sortedCoordinates = $sheet->getCellCollection()->getSortedCoordinates();
$coordinateTopLeft = reset($sortedCoordinates);
self::assertSame('A1', $coordinateTopLeft);
}
/**
* Test load Xlsx file with many empty cells (and big max row number) with readfilter.
*
* @dataProvider providerCellsValues
*
* @param array $arrayData
* @param mixed $format
*/
public function testXlsxLoadWithReadFilter($format, array $arrayData)
{
$spreadsheet = new Spreadsheet();
$spreadsheet->getActiveSheet()->fromArray($arrayData, null, 'A1');
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $format, function ($reader) {
// Create a stub for the readFilter class.
$readFilterStub = $this->createMock(IReadFilter::class);
$readFilterStub->method('readCell')
->will($this->returnCallback([$this, 'readFilterReadCell']));
// apply filter
$reader->setReadFilter($readFilterStub);
});
$sheet = $reloadedSpreadsheet->getSheet(0);
// test highest column (very specific num of columns because of some 3rd party software)
self::assertSame('D', $sheet->getHighestColumn());
// test highest row (very specific num of rows because of some 3rd party software)
self::assertEquals(6, $sheet->getHighestRow());
// test top left coordinate
$sortedCoordinates = $sheet->getCellCollection()->getSortedCoordinates();
$coordinateTopLeft = reset($sortedCoordinates);
self::assertSame('B2', $coordinateTopLeft);
}
/**
* @see \PhpOffice\PhpSpreadsheet\Reader\IReadFilter::readCell()
*
* @param string $column Column address (as a string value like "A", or "IV")
* @param int $row Row number
* @param string $worksheetName Optional worksheet name
*
* @return bool
*/
public function readFilterReadCell($column, $row, $worksheetName = '')
{
// define filter range
$rowMin = 2;
$rowMax = 6;
$columnMin = 'B';
$columnMax = 'D';
$r = (int) $row;
if ($r > $rowMax || $r < $rowMin) {
return false;
}
$col = sprintf('%04s', $column);
if ($col > sprintf('%04s', $columnMax) ||
$col < sprintf('%04s', $columnMin)) {
return false;
}
return true;
}
}