Files
Dmytro Kulyk d8c46b48f9 Xlsx writer: a header-row table takes the empty row below it
A table whose range covers its header row alone makes Excel report the
workbook as unreadable; it repairs the file by dropping the table, so the
markup the table was written for is lost without a word.

Excel itself never writes such a table. Asked to make one over a single row
of headings, it writes the table over the row below as well and leaves that
row empty — its `sheetData` holds no cell for it, and the sheet dimension
stays at the headings. The writer now does the same, so what is written is
what Excel would have written.

The row below is taken only when it holds nothing. A table silently
swallowing a row that belongs to something else would change what the sheet
says, so that case throws instead, naming the row in the way.
2026-08-20 00:29:44 +03:00

97 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
/**
* Excel reports a workbook whose table covers its header row alone as unreadable, and repairs it
* by dropping the table. Excel itself never writes one: asked to make a table over a single row of
* headings it writes the table over the row below as well, leaving that row without a cell. The
* writer does the same — unless that row already holds something.
*/
class TableHeaderRowTest extends AbstractFunctional
{
private ?Spreadsheet $spreadsheet = null;
protected function tearDown(): void
{
if ($this->spreadsheet !== null) {
$this->spreadsheet->disconnectWorksheets();
$this->spreadsheet = null;
}
}
public function testHeaderRowTakesTheEmptyRowBelowIt(): void
{
$this->spreadsheet = new Spreadsheet();
$sheet = $this->spreadsheet->getActiveSheet();
$sheet->fromArray([['Year', 'Country']], null, 'A1');
$sheet->addTable(new Table('A1:B1', 'SalesData'));
$reloaded = $this->writeAndReload($this->spreadsheet, 'Xlsx');
$worksheet = $reloaded->getActiveSheet();
$table = $worksheet->getTableByName('SalesData');
// exactly what Excel writes for a table made over a single row of headings
self::assertInstanceOf(Table::class, $table);
self::assertSame('A1:B2', $table->getRange());
self::assertSame(1, $worksheet->getHighestDataRow(), 'The row taken is left without a cell');
$reloaded->disconnectWorksheets();
}
public function testHeaderRowWillNotSwallowARowThatHoldsSomething(): void
{
$this->spreadsheet = new Spreadsheet();
$sheet = $this->spreadsheet->getActiveSheet();
$sheet->fromArray([['Year', 'Country']], null, 'A1');
$sheet->getCell('A2')->setValue('Total');
$sheet->addTable(new Table('A1:B1', 'SalesData'));
$this->expectException(PhpSpreadsheetException::class);
$this->expectExceptionMessage('needs at least 2 rows');
$this->writeAndReload($this->spreadsheet, 'Xlsx')->disconnectWorksheets();
}
public function testHeaderRowWithOneRowOfDataIsWritten(): void
{
$this->spreadsheet = new Spreadsheet();
$sheet = $this->spreadsheet->getActiveSheet();
$sheet->fromArray([['Year', 'Country'], [2010, 'Belgium']], null, 'A1');
$sheet->addTable(new Table('A1:B2', 'SalesData'));
$reloaded = $this->writeAndReload($this->spreadsheet, 'Xlsx');
$table = $reloaded->getActiveSheet()->getTableByName('SalesData');
self::assertInstanceOf(Table::class, $table);
self::assertSame('A1:B2', $table->getRange());
self::assertTrue($table->getShowHeaderRow());
$reloaded->disconnectWorksheets();
}
public function testASingleRowTableWithoutAHeaderRowIsWritten(): void
{
$this->spreadsheet = new Spreadsheet();
$sheet = $this->spreadsheet->getActiveSheet();
$sheet->fromArray([[2010, 'Belgium']], null, 'A1');
$sheet->addTable((new Table('A1:B1', 'SalesData'))->setShowHeaderRow(false));
$reloaded = $this->writeAndReload($this->spreadsheet, 'Xlsx');
$table = $reloaded->getActiveSheet()->getTableByName('SalesData');
self::assertInstanceOf(Table::class, $table);
self::assertSame('A1:B1', $table->getRange());
self::assertFalse($table->getShowHeaderRow());
$reloaded->disconnectWorksheets();
}
}