Skip to content

Commit

Permalink
Xlsx reader do not read rows and columns filtered out in readFilter a…
Browse files Browse the repository at this point in the history
…t all

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

Fixes PHPOffice#370
Closes PHPOffice#421
  • Loading branch information
xklid101 authored and Frederic Delaunay committed Oct 29, 2018
1 parent 8b43c40 commit 03d64d3
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions src/PhpSpreadsheet/Reader/IReadFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
136 changes: 109 additions & 27 deletions src/PhpSpreadsheet/Reader/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,60 @@ private function getFromZipArchive(ZipArchive $archive, $fileName = '')
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.
*
Expand Down Expand Up @@ -819,22 +873,39 @@ public function load($pFilename)
}
}

if (isset($xmlSheet->printOptions) && !$this->readDataOnly) {
if (self::boolean((string) $xmlSheet->printOptions['gridLinesSet'])) {
$docSheet->setShowGridlines(true);
}
if (self::boolean((string) $xmlSheet->printOptions['gridLines'])) {
$docSheet->setPrintGridlines(true);
}
if (self::boolean((string) $xmlSheet->printOptions['horizontalCentered'])) {
$docSheet->getPageSetup()->setHorizontalCentered(true);
}
if (self::boolean((string) $xmlSheet->printOptions['verticalCentered'])) {
$docSheet->getPageSetup()->setVerticalCentered(true);
}
}

$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) {
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setXfIndex((int) ($col['style']));
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['xfIndex'] = (int) $col['style'];
}
if (self::boolean($col['hidden'])) {
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setVisible(false);
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['visible'] = false;
}
if (self::boolean($col['collapsed'])) {
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setCollapsed(true);
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['collapsed'] = true;
}
if ($col['outlineLevel'] > 0) {
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setOutlineLevel((int) ($col['outlineLevel']));
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['outlineLevel'] = (int) $col['outlineLevel'];
}
$docSheet->getColumnDimension(Coordinate::stringFromColumnIndex($i))->setWidth((float) ($col['width']));
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['width'] = (float) $col['width'];

if ((int) ($col['max']) == 16384) {
break;
Expand All @@ -843,40 +914,51 @@ public function load($pFilename)
}
}

if (isset($xmlSheet->printOptions) && !$this->readDataOnly) {
if (self::boolean((string) $xmlSheet->printOptions['gridLinesSet'])) {
$docSheet->setShowGridlines(true);
}
if (self::boolean((string) $xmlSheet->printOptions['gridLines'])) {
$docSheet->setPrintGridlines(true);
}
if (self::boolean((string) $xmlSheet->printOptions['horizontalCentered'])) {
$docSheet->getPageSetup()->setHorizontalCentered(true);
}
if (self::boolean((string) $xmlSheet->printOptions['verticalCentered'])) {
$docSheet->getPageSetup()->setVerticalCentered(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']));
$rowsAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
}
if (self::boolean($row['hidden']) && !$this->readDataOnly) {
$docSheet->getRowDimension((int) ($row['r']))->setVisible(false);
$rowsAttributes[(int) $row['r']]['visible'] = false;
}
if (self::boolean($row['collapsed'])) {
$docSheet->getRowDimension((int) ($row['r']))->setCollapsed(true);
$rowsAttributes[(int) $row['r']]['collapsed'] = true;
}
if ($row['outlineLevel'] > 0) {
$docSheet->getRowDimension((int) ($row['r']))->setOutlineLevel((int) ($row['outlineLevel']));
$rowsAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
}
if ($row['s'] && !$this->readDataOnly) {
$docSheet->getRowDimension((int) ($row['r']))->setXfIndex((int) ($row['s']));
$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) {
$rowIndex = 1;
foreach ($row->c as $c) {
$r = (string) $c['r'];
Expand All @@ -891,7 +973,7 @@ public function load($pFilename)
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;
}
}
Expand Down
124 changes: 124 additions & 0 deletions tests/PhpSpreadsheetTests/Functional/ReadFilterTest.php
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 03d64d3

Please sign in to comment.