Merge pull request #3111 from PHPOffice/Issue-3102_Allow-AutoFilter-on-Single-Cell
Allow AutoFilter on single cell, or a single row
This commit is contained in:
commit
4adbfd86e6
|
|
@ -9,7 +9,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
|
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
|
||||||
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
|
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
|
||||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
use PhpOffice\PhpSpreadsheet\Exception;
|
||||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||||
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
|
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
|
||||||
|
|
||||||
|
|
@ -104,7 +104,7 @@ class AutoFilter
|
||||||
* Set AutoFilter Cell Range.
|
* Set AutoFilter Cell Range.
|
||||||
*
|
*
|
||||||
* @param AddressRange|array<int>|string $range
|
* @param AddressRange|array<int>|string $range
|
||||||
* A simple string containing a Cell range like 'A1:E10' is permitted
|
* A simple string containing a Cell range like 'A1:E10' or a Cell address like 'A1' is permitted
|
||||||
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
|
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
|
||||||
* or an AddressRange object.
|
* or an AddressRange object.
|
||||||
*/
|
*/
|
||||||
|
|
@ -115,6 +115,7 @@ class AutoFilter
|
||||||
if ($range !== '') {
|
if ($range !== '') {
|
||||||
[, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true);
|
[, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($range)) {
|
if (empty($range)) {
|
||||||
// Discard all column rules
|
// Discard all column rules
|
||||||
$this->columns = [];
|
$this->columns = [];
|
||||||
|
|
@ -123,8 +124,8 @@ class AutoFilter
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strpos($range, ':') === false) {
|
if (ctype_digit($range) || ctype_alpha($range)) {
|
||||||
throw new PhpSpreadsheetException('Autofilter must be set on a range of cells.');
|
throw new Exception("{$range} is an invalid range for AutoFilter");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->range = $range;
|
$this->range = $range;
|
||||||
|
|
@ -174,13 +175,13 @@ class AutoFilter
|
||||||
public function testColumnInRange($column)
|
public function testColumnInRange($column)
|
||||||
{
|
{
|
||||||
if (empty($this->range)) {
|
if (empty($this->range)) {
|
||||||
throw new PhpSpreadsheetException('No autofilter range is defined.');
|
throw new Exception('No autofilter range is defined.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$columnIndex = Coordinate::columnIndexFromString($column);
|
$columnIndex = Coordinate::columnIndexFromString($column);
|
||||||
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
|
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
|
||||||
if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) {
|
if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) {
|
||||||
throw new PhpSpreadsheetException('Column is outside of current autofilter range.');
|
throw new Exception('Column is outside of current autofilter range.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $columnIndex - $rangeStart[0];
|
return $columnIndex - $rangeStart[0];
|
||||||
|
|
@ -247,7 +248,7 @@ class AutoFilter
|
||||||
} elseif (is_object($columnObjectOrString) && ($columnObjectOrString instanceof AutoFilter\Column)) {
|
} elseif (is_object($columnObjectOrString) && ($columnObjectOrString instanceof AutoFilter\Column)) {
|
||||||
$column = $columnObjectOrString->getColumnIndex();
|
$column = $columnObjectOrString->getColumnIndex();
|
||||||
} else {
|
} else {
|
||||||
throw new PhpSpreadsheetException('Column is not within the autofilter range.');
|
throw new Exception('Column is not within the autofilter range.');
|
||||||
}
|
}
|
||||||
$this->testColumnInRange($column);
|
$this->testColumnInRange($column);
|
||||||
|
|
||||||
|
|
@ -1031,6 +1032,8 @@ class AutoFilter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$rangeEnd[1] = $this->autoExtendRange($rangeStart[1], $rangeEnd[1]);
|
||||||
|
|
||||||
// Execute the column tests for each row in the autoFilter range to determine show/hide,
|
// Execute the column tests for each row in the autoFilter range to determine show/hide,
|
||||||
for ($row = $rangeStart[1] + 1; $row <= $rangeEnd[1]; ++$row) {
|
for ($row = $rangeStart[1] + 1; $row <= $rangeEnd[1]; ++$row) {
|
||||||
$result = true;
|
$result = true;
|
||||||
|
|
@ -1053,6 +1056,29 @@ class AutoFilter
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic Range Auto-sizing.
|
||||||
|
* For a single row rangeSet, we follow MS Excel rules, and search for the first empty row to determine our range.
|
||||||
|
*/
|
||||||
|
public function autoExtendRange(int $startRow, int $endRow): int
|
||||||
|
{
|
||||||
|
if ($startRow === $endRow && $this->workSheet !== null) {
|
||||||
|
try {
|
||||||
|
$rowIterator = $this->workSheet->getRowIterator($startRow + 1);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// If there are no rows below $startRow
|
||||||
|
return $startRow;
|
||||||
|
}
|
||||||
|
foreach ($rowIterator as $row) {
|
||||||
|
if ($row->isEmpty(CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL) === true) {
|
||||||
|
return $row->getRowIndex() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $endRow;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implement PHP __clone to create a deep clone, not just a shallow copy.
|
* Implement PHP __clone to create a deep clone, not just a shallow copy.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,9 @@ class DefinedNames
|
||||||
[, $range[0]] = Worksheet::extractSheetTitle($range[0], true);
|
[, $range[0]] = Worksheet::extractSheetTitle($range[0], true);
|
||||||
|
|
||||||
$range[0] = Coordinate::absoluteCoordinate($range[0]);
|
$range[0] = Coordinate::absoluteCoordinate($range[0]);
|
||||||
$range[1] = Coordinate::absoluteCoordinate($range[1]);
|
if (count($range) > 1) {
|
||||||
|
$range[1] = Coordinate::absoluteCoordinate($range[1]);
|
||||||
|
}
|
||||||
$range = implode(':', $range);
|
$range = implode(':', $range);
|
||||||
|
|
||||||
$this->objWriter->writeRawData('\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!' . $range);
|
$this->objWriter->writeRawData('\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!' . $range);
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ class AutoFilterTest extends SetupTeardown
|
||||||
$ranges = [
|
$ranges = [
|
||||||
'G1:J512' => "$title!G1:J512",
|
'G1:J512' => "$title!G1:J512",
|
||||||
'K1:N20' => 'K1:N20',
|
'K1:N20' => 'K1:N20',
|
||||||
|
'B10' => 'B10',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($ranges as $actualRange => $fullRange) {
|
foreach ($ranges as $actualRange => $fullRange) {
|
||||||
|
|
@ -94,11 +95,22 @@ class AutoFilterTest extends SetupTeardown
|
||||||
self::assertEquals($expectedResult, $result);
|
self::assertEquals($expectedResult, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetRangeInvalidRange(): void
|
public function testSetRangeInvalidRowRange(): void
|
||||||
{
|
{
|
||||||
$this->expectException(PhpSpreadsheetException::class);
|
$this->expectException(PhpSpreadsheetException::class);
|
||||||
|
|
||||||
$expectedResult = 'A1';
|
$expectedResult = '999';
|
||||||
|
|
||||||
|
$sheet = $this->getSheet();
|
||||||
|
$autoFilter = $sheet->getAutoFilter();
|
||||||
|
$autoFilter->setRange($expectedResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetRangeInvalidColumnRange(): void
|
||||||
|
{
|
||||||
|
$this->expectException(PhpSpreadsheetException::class);
|
||||||
|
|
||||||
|
$expectedResult = 'ABC';
|
||||||
|
|
||||||
$sheet = $this->getSheet();
|
$sheet = $this->getSheet();
|
||||||
$autoFilter = $sheet->getAutoFilter();
|
$autoFilter = $sheet->getAutoFilter();
|
||||||
|
|
@ -498,4 +510,26 @@ class AutoFilterTest extends SetupTeardown
|
||||||
self::assertArrayHasKey('K', $columns);
|
self::assertArrayHasKey('K', $columns);
|
||||||
self::assertArrayHasKey('M', $columns);
|
self::assertArrayHasKey('M', $columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testAutoExtendRange(): void
|
||||||
|
{
|
||||||
|
$spreadsheet = $this->getSpreadsheet();
|
||||||
|
$worksheet = $spreadsheet->addSheet(new Worksheet($spreadsheet, 'Autosized AutoFilter'));
|
||||||
|
|
||||||
|
$worksheet->getCell('A1')->setValue('Col 1');
|
||||||
|
$worksheet->getCell('B1')->setValue('Col 2');
|
||||||
|
|
||||||
|
$worksheet->setAutoFilter('A1:B1');
|
||||||
|
$lastRow = $worksheet->getAutoFilter()->autoExtendRange(1, 1);
|
||||||
|
self::assertSame(1, $lastRow, 'No data below AutoFilter, so there should ne no resize');
|
||||||
|
|
||||||
|
$lastRow = $worksheet->getAutoFilter()->autoExtendRange(1, 999);
|
||||||
|
self::assertSame(999, $lastRow, 'Filter range is already correctly sized');
|
||||||
|
|
||||||
|
$data = [['A', 'A'], ['B', 'A'], ['A', 'B'], ['C', 'B'], ['B', null], [null, null], ['D', 'D'], ['E', 'E']];
|
||||||
|
$worksheet->fromArray($data, null, 'A2', true);
|
||||||
|
|
||||||
|
$lastRow = $worksheet->getAutoFilter()->autoExtendRange(1, 1);
|
||||||
|
self::assertSame(6, $lastRow, 'Filter range has been re-sized incorrectly');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue