Skip to content

Commit

Permalink
CSV Writer Allow Varying Number of Columns
Browse files Browse the repository at this point in the history
Supersedes PR PHPOffice#1415 by @AndrewMonty, which went stale in May 2020, and which is not directly usable due to changes between now and then. Fix PHPOffice#1414, which also went stale; I will remove the stale status and reopen the issue pending the merging of this PR.

Add an option to CSV Writer so that it writes the cells for a row only through the highest data column used in the row, rather than through the highest data column used in the worksheet.
  • Loading branch information
oleibman committed Jun 27, 2024
1 parent 318a82e commit 02479de
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Added

- Xlsx Reader Optionally Ignore Rows With No Cells. [Issue #3982](https://github.com/PHPOffice/PhpSpreadsheet/issues/3982) [PR #4035](https://github.com/PHPOffice/PhpSpreadsheet/pull/4035)
- Option for CSV output file to have varying numbers of columns for each row. [Issue #1415](https://github.com/PHPOffice/PhpSpreadsheet/issues/1415) [PR #4076](https://github.com/PHPOffice/PhpSpreadsheet/pull/4076)

### Changed

Expand Down
12 changes: 12 additions & 0 deletions docs/topics/reading-and-writing-to-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,18 @@ $writer->setOutputEncoding('SJIS-WIN');
$writer->save("05featuredemo.csv");
```

#### Writing CSV files with varying numbers of columns

A CSV file can have a different number of columns in each row. This
differs from the default behavior when saving as a .csv in Excel, but
can be enabled in PhpSpreadsheet by using the following code:

``` php
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet);
$writer->setVariableColumns(true);
$writer->save("05featuredemo.csv");
```

#### Decimal and thousands separators

If the worksheet you are exporting contains numbers with decimal or
Expand Down
40 changes: 40 additions & 0 deletions src/PhpSpreadsheet/Writer/Csv.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Writer;

use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use Stringable;

Expand Down Expand Up @@ -54,6 +55,13 @@ class Csv extends BaseWriter
*/
private string $outputEncoding = '';

/**
* Whether number of columns should be allowed to vary
* between rows, or use a fixed range based on the max
* column overall.
*/
private bool $variableColumns = false;

/**
* Create a new CSV.
*/
Expand Down Expand Up @@ -105,7 +113,17 @@ public function save($filename, int $flags = 0): void
$maxRow = $sheet->getHighestDataRow();

// Write rows to file
$row = 0;
foreach ($sheet->rangeToArrayYieldRows("A1:$maxCol$maxRow", '', $this->preCalculateFormulas) as $cellsArray) {
++$row;
if ($this->variableColumns) {
$column = $sheet->getHighestDataColumn($row);
if ($column === 'A' && !$sheet->cellExists("A$row")) {
$cellsArray = [];
} else {
array_splice($cellsArray, Coordinate::columnIndexFromString($column));
}
}
$this->writeLine($this->fileHandle, $cellsArray);
}

Expand Down Expand Up @@ -303,4 +321,26 @@ private function writeLine($fileHandle, array $values): void
}
fwrite($fileHandle, $line);
}

/**
* Get whether number of columns should be allowed to vary
* between rows, or use a fixed range based on the max
* column overall.
*/
public function getVariableColumns(): bool
{
return $this->variableColumns;
}

/**
* Set whether number of columns should be allowed to vary
* between rows, or use a fixed range based on the max
* column overall.
*/
public function setVariableColumns(bool $pValue): self
{
$this->variableColumns = $pValue;

return $this;
}
}
81 changes: 81 additions & 0 deletions tests/PhpSpreadsheetTests/Writer/Csv/VariableColumnsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests\Writer\Csv;

use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Csv;
use PHPUnit\Framework\TestCase;

class VariableColumnsTest extends TestCase
{
public function testVariableColumns(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->fromArray(
[
[1, 2, 3, 4],
[1, 2],
[1, 2, 3, 4, 5],
[],
[1],
[1, 2, 3],
]
);

$filename = File::temporaryFilename();
$writer = new Csv($spreadsheet);
$writer->setVariableColumns(true);
$writer->save($filename);

$contents = (string) file_get_contents($filename);
unlink($filename);
$spreadsheet->disconnectWorksheets();

$rows = explode(PHP_EOL, $contents);

self::assertSame('"1","2","3","4"', $rows[0]);
self::assertSame('"1","2"', $rows[1]);
self::assertSame('"1","2","3","4","5"', $rows[2]);
self::assertSame('', $rows[3]);
self::assertSame('"1"', $rows[4]);
self::assertSame('"1","2","3"', $rows[5]);
}

public function testFixedColumns(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->fromArray(
[
[1, 2, 3, 4],
[1, 2],
[1, 2, 3, 4, 5],
[],
[1],
[1, 2, 3],
]
);

$filename = File::temporaryFilename();
$writer = new Csv($spreadsheet);
self::assertFalse($writer->getVariableColumns());
$writer->save($filename);

$contents = (string) file_get_contents($filename);
unlink($filename);
$spreadsheet->disconnectWorksheets();

$rows = explode(PHP_EOL, $contents);

self::assertSame('"1","2","3","4",""', $rows[0]);
self::assertSame('"1","2","","",""', $rows[1]);
self::assertSame('"1","2","3","4","5"', $rows[2]);
self::assertSame('"","","","",""', $rows[3]);
self::assertSame('"1","","","",""', $rows[4]);
self::assertSame('"1","2","3","",""', $rows[5]);
}
}

0 comments on commit 02479de

Please sign in to comment.