Skip to content

Commit 02479de

Browse files
committed
CSV Writer Allow Varying Number of Columns
Supersedes PR #1415 by @AndrewMonty, which went stale in May 2020, and which is not directly usable due to changes between now and then. Fix #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.
1 parent 318a82e commit 02479de

File tree

4 files changed

+134
-0
lines changed

4 files changed

+134
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
1010
### Added
1111

1212
- 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)
13+
- 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)
1314

1415
### Changed
1516

docs/topics/reading-and-writing-to-file.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,18 @@ $writer->setOutputEncoding('SJIS-WIN');
679679
$writer->save("05featuredemo.csv");
680680
```
681681

682+
#### Writing CSV files with varying numbers of columns
683+
684+
A CSV file can have a different number of columns in each row. This
685+
differs from the default behavior when saving as a .csv in Excel, but
686+
can be enabled in PhpSpreadsheet by using the following code:
687+
688+
``` php
689+
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet);
690+
$writer->setVariableColumns(true);
691+
$writer->save("05featuredemo.csv");
692+
```
693+
682694
#### Decimal and thousands separators
683695

684696
If the worksheet you are exporting contains numbers with decimal or

src/PhpSpreadsheet/Writer/Csv.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpSpreadsheet\Writer;
44

55
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6+
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
67
use PhpOffice\PhpSpreadsheet\Spreadsheet;
78
use Stringable;
89

@@ -54,6 +55,13 @@ class Csv extends BaseWriter
5455
*/
5556
private string $outputEncoding = '';
5657

58+
/**
59+
* Whether number of columns should be allowed to vary
60+
* between rows, or use a fixed range based on the max
61+
* column overall.
62+
*/
63+
private bool $variableColumns = false;
64+
5765
/**
5866
* Create a new CSV.
5967
*/
@@ -105,7 +113,17 @@ public function save($filename, int $flags = 0): void
105113
$maxRow = $sheet->getHighestDataRow();
106114

107115
// Write rows to file
116+
$row = 0;
108117
foreach ($sheet->rangeToArrayYieldRows("A1:$maxCol$maxRow", '', $this->preCalculateFormulas) as $cellsArray) {
118+
++$row;
119+
if ($this->variableColumns) {
120+
$column = $sheet->getHighestDataColumn($row);
121+
if ($column === 'A' && !$sheet->cellExists("A$row")) {
122+
$cellsArray = [];
123+
} else {
124+
array_splice($cellsArray, Coordinate::columnIndexFromString($column));
125+
}
126+
}
109127
$this->writeLine($this->fileHandle, $cellsArray);
110128
}
111129

@@ -303,4 +321,26 @@ private function writeLine($fileHandle, array $values): void
303321
}
304322
fwrite($fileHandle, $line);
305323
}
324+
325+
/**
326+
* Get whether number of columns should be allowed to vary
327+
* between rows, or use a fixed range based on the max
328+
* column overall.
329+
*/
330+
public function getVariableColumns(): bool
331+
{
332+
return $this->variableColumns;
333+
}
334+
335+
/**
336+
* Set whether number of columns should be allowed to vary
337+
* between rows, or use a fixed range based on the max
338+
* column overall.
339+
*/
340+
public function setVariableColumns(bool $pValue): self
341+
{
342+
$this->variableColumns = $pValue;
343+
344+
return $this;
345+
}
306346
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Csv;
6+
7+
use PhpOffice\PhpSpreadsheet\Shared\File;
8+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9+
use PhpOffice\PhpSpreadsheet\Writer\Csv;
10+
use PHPUnit\Framework\TestCase;
11+
12+
class VariableColumnsTest extends TestCase
13+
{
14+
public function testVariableColumns(): void
15+
{
16+
$spreadsheet = new Spreadsheet();
17+
$sheet = $spreadsheet->getActiveSheet();
18+
$sheet->fromArray(
19+
[
20+
[1, 2, 3, 4],
21+
[1, 2],
22+
[1, 2, 3, 4, 5],
23+
[],
24+
[1],
25+
[1, 2, 3],
26+
]
27+
);
28+
29+
$filename = File::temporaryFilename();
30+
$writer = new Csv($spreadsheet);
31+
$writer->setVariableColumns(true);
32+
$writer->save($filename);
33+
34+
$contents = (string) file_get_contents($filename);
35+
unlink($filename);
36+
$spreadsheet->disconnectWorksheets();
37+
38+
$rows = explode(PHP_EOL, $contents);
39+
40+
self::assertSame('"1","2","3","4"', $rows[0]);
41+
self::assertSame('"1","2"', $rows[1]);
42+
self::assertSame('"1","2","3","4","5"', $rows[2]);
43+
self::assertSame('', $rows[3]);
44+
self::assertSame('"1"', $rows[4]);
45+
self::assertSame('"1","2","3"', $rows[5]);
46+
}
47+
48+
public function testFixedColumns(): void
49+
{
50+
$spreadsheet = new Spreadsheet();
51+
$sheet = $spreadsheet->getActiveSheet();
52+
$sheet->fromArray(
53+
[
54+
[1, 2, 3, 4],
55+
[1, 2],
56+
[1, 2, 3, 4, 5],
57+
[],
58+
[1],
59+
[1, 2, 3],
60+
]
61+
);
62+
63+
$filename = File::temporaryFilename();
64+
$writer = new Csv($spreadsheet);
65+
self::assertFalse($writer->getVariableColumns());
66+
$writer->save($filename);
67+
68+
$contents = (string) file_get_contents($filename);
69+
unlink($filename);
70+
$spreadsheet->disconnectWorksheets();
71+
72+
$rows = explode(PHP_EOL, $contents);
73+
74+
self::assertSame('"1","2","3","4",""', $rows[0]);
75+
self::assertSame('"1","2","","",""', $rows[1]);
76+
self::assertSame('"1","2","3","4","5"', $rows[2]);
77+
self::assertSame('"","","","",""', $rows[3]);
78+
self::assertSame('"1","","","",""', $rows[4]);
79+
self::assertSame('"1","2","3","",""', $rows[5]);
80+
}
81+
}

0 commit comments

Comments
 (0)