Skip to content

Commit 98b8365

Browse files
committed
Fixed flat file writing on multiple sheets
1 parent 687f2f0 commit 98b8365

File tree

3 files changed

+166
-7
lines changed

3 files changed

+166
-7
lines changed

src/batch-box-spout/src/Writer/FlatFileWriter.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
99
use Box\Spout\Writer\Common\Creator\WriterFactory;
1010
use Box\Spout\Writer\WriterInterface;
11+
use Box\Spout\Writer\WriterMultiSheetsAbstract;
1112
use Yokai\Batch\Bridge\Box\Spout\Writer\Options\OptionsInterface;
1213
use Yokai\Batch\Exception\BadMethodCallException;
1314
use Yokai\Batch\Exception\RuntimeException;
@@ -40,6 +41,7 @@ final class FlatFileWriter implements
4041
private ?array $headers;
4142
private ?WriterInterface $writer = null;
4243
private bool $headersAdded = false;
44+
private ?string $defaultSheet = null;
4345

4446
/**
4547
* @phpstan-param list<string>|null $headers
@@ -70,6 +72,10 @@ public function initialize(): void
7072
$this->writer = WriterFactory::createFromFile($path);
7173
$this->writer->openToFile($path);
7274
$this->options->configure($this->writer);
75+
76+
if ($this->writer instanceof WriterMultiSheetsAbstract) {
77+
$this->defaultSheet = $this->writer->getCurrentSheet()->getName();
78+
}
7379
}
7480

7581
/**
@@ -89,6 +95,12 @@ public function write(iterable $items): void
8995
}
9096

9197
foreach ($items as $row) {
98+
if ($row instanceof WriteToSheetItem) {
99+
$this->changeSheet($row->getSheet());
100+
$row = $row->getItem();
101+
} elseif ($this->defaultSheet !== null) {
102+
$this->changeSheet($this->defaultSheet);
103+
}
92104
if (\is_array($row)) {
93105
$row = WriterEntityFactory::createRowFromArray($row);
94106
}
@@ -113,4 +125,21 @@ public function flush(): void
113125
$this->writer = null;
114126
$this->headersAdded = false;
115127
}
128+
129+
private function changeSheet(string $name): void
130+
{
131+
if (!$this->writer instanceof WriterMultiSheetsAbstract) {
132+
return;
133+
}
134+
135+
foreach ($this->writer->getSheets() as $sheet) {
136+
if ($sheet->getName() === $name) {
137+
$this->writer->setCurrentSheet($sheet);
138+
return;
139+
}
140+
}
141+
142+
$sheet = $this->writer->addNewSheetAndMakeItCurrent();
143+
$sheet->setName($name);
144+
}
116145
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Yokai\Batch\Bridge\Box\Spout\Writer;
4+
5+
use Box\Spout\Common\Entity\Row;
6+
use Box\Spout\Common\Entity\Style\Style;
7+
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
8+
9+
final class WriteToSheetItem
10+
{
11+
private string $sheet;
12+
private Row $item;
13+
14+
private function __construct(string $sheet, Row $item)
15+
{
16+
$this->sheet = $sheet;
17+
$this->item = $item;
18+
}
19+
20+
public static function array(string $sheet, array $item, Style $style = null): self
21+
{
22+
return new self($sheet, WriterEntityFactory::createRowFromArray($item, $style));
23+
}
24+
25+
public static function row(string $sheet, Row $item): self
26+
{
27+
return new self($sheet, $item);
28+
}
29+
30+
public function getSheet(): string
31+
{
32+
return $this->sheet;
33+
}
34+
35+
public function getItem(): Row
36+
{
37+
return $this->item;
38+
}
39+
}

src/batch-box-spout/tests/Writer/FlatFileWriterTest.php

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66

77
use Box\Spout\Common\Entity\Style\CellAlignment;
88
use Box\Spout\Common\Entity\Style\Color;
9-
use Box\Spout\Reader\Wrapper\XMLReader;
109
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;
1110
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
1211
use PHPUnit\Framework\TestCase;
1312
use Yokai\Batch\Bridge\Box\Spout\Writer\FlatFileWriter;
1413
use Yokai\Batch\Bridge\Box\Spout\Writer\Options\CSVOptions;
1514
use Yokai\Batch\Bridge\Box\Spout\Writer\Options\ODSOptions;
1615
use Yokai\Batch\Bridge\Box\Spout\Writer\Options\XLSXOptions;
16+
use Yokai\Batch\Bridge\Box\Spout\Writer\WriteToSheetItem;
1717
use Yokai\Batch\Exception\BadMethodCallException;
1818
use Yokai\Batch\Exception\RuntimeException;
1919
use Yokai\Batch\Exception\UnexpectedValueException;
@@ -35,7 +35,6 @@ public function testWrite(
3535
string $expectedContent
3636
): void {
3737
$file = self::WRITE_DIR . '/' . $filename;
38-
3938
self::assertFileDoesNotExist($file);
4039

4140
$writer = new FlatFileWriter(new StaticValueParameterAccessor($file), $options(), $headers);
@@ -219,6 +218,58 @@ public function types(): \Generator
219218
yield [$type, $options];
220219
}
221220
}
221+
222+
/**
223+
* @dataProvider multipleSheetsOptions
224+
*/
225+
public function testWriteMultipleSheets(string $type, callable $options): void
226+
{
227+
$file = self::WRITE_DIR . '/multiple-sheets.' . $type;
228+
self::assertFileDoesNotExist($file);
229+
230+
$writer = new FlatFileWriter(new StaticValueParameterAccessor($file), $options());
231+
$writer->setJobExecution(JobExecution::createRoot('123456789', 'export'));
232+
233+
$writer->initialize();
234+
$writer->write([
235+
WriteToSheetItem::array('English', ['John', 'Doe']),
236+
WriteToSheetItem::array('Français', ['Jean', 'Aimar']),
237+
WriteToSheetItem::row('English', WriterEntityFactory::createRowFromArray(['Jack', 'Doe'])),
238+
WriteToSheetItem::row('Français', WriterEntityFactory::createRowFromArray(['Jacques', 'Ouzi'])),
239+
]);
240+
$writer->flush();
241+
242+
if ($type === 'csv') {
243+
self::assertFileContents($file, <<<CSV
244+
John,Doe
245+
Jean,Aimar
246+
Jack,Doe
247+
Jacques,Ouzi
248+
CSV);
249+
} else {
250+
self::assertSheetContents($file, 'English', <<<CSV
251+
John,Doe
252+
Jack,Doe
253+
CSV);
254+
self::assertSheetContents($file, 'Français', <<<CSV
255+
Jean,Aimar
256+
Jacques,Ouzi
257+
CSV);
258+
}
259+
}
260+
261+
public function multipleSheetsOptions(): \Generator
262+
{
263+
$types = [
264+
'csv' => fn() => new CSVOptions(),
265+
'xlsx' => fn() => new XLSXOptions('English'),
266+
'ods' => fn() => new ODSOptions('English'),
267+
];
268+
foreach ($types as $type => $options) {
269+
yield [$type, $options];
270+
}
271+
}
272+
222273
/**
223274
* @dataProvider wrongOptions
224275
*/
@@ -283,15 +334,55 @@ private static function assertFileContents(string $filePath, string $inlineData)
283334
$pathToSheetFile = $filePath . '#xl/worksheets/sheet1.xml';
284335
$xmlContents = file_get_contents('zip://' . $pathToSheetFile);
285336
foreach ($strings as $string) {
286-
self::assertStringContainsString($string, $xmlContents);
337+
self::assertStringContainsString("<t>$string</t>", $xmlContents);
287338
}
288339
break;
289340

290341
case 'ods':
291-
$xmlReader = new XMLReader();
292-
$xmlReader->openFileInZip($filePath, 'content.xml');
293-
$xmlReader->readUntilNodeFound('table:table');
294-
$sheetXmlAsString = $xmlReader->readOuterXml();
342+
$sheetContent = file_get_contents('zip://' . $filePath . '#content.xml');
343+
if (!preg_match('#<table:table[^>]+>[\s\S]*?<\/table:table>#', $sheetContent, $matches)) {
344+
self::fail('No sheet found in file "' . $filePath . '".');
345+
}
346+
$sheetXmlAsString = $matches[0];
347+
foreach ($strings as $string) {
348+
self::assertStringContainsString("<text:p>$string</text:p>", $sheetXmlAsString);
349+
}
350+
break;
351+
}
352+
}
353+
354+
private static function assertSheetContents(string $filePath, string $sheet, string $inlineData): void
355+
{
356+
$type = \strtolower(\pathinfo($filePath, PATHINFO_EXTENSION));
357+
$strings = array_merge(...array_map('str_getcsv', explode(PHP_EOL, $inlineData)));
358+
359+
switch ($type) {
360+
case 'csv':
361+
$fileContents = file_get_contents($filePath);
362+
foreach ($strings as $string) {
363+
self::assertStringContainsString($string, $fileContents);
364+
}
365+
break;
366+
367+
case 'xlsx':
368+
$workbookContent = file_get_contents('zip://' . $filePath . '#xl/workbook.xml');
369+
if (!preg_match('#<sheet name="' . $sheet . '" sheetId="([0-9]+)"#', $workbookContent, $matches)) {
370+
self::fail('Sheet ' . $sheet . ' was not found in file "' . $filePath . '".');
371+
}
372+
$sheetFilename = 'sheet' . $matches[1];
373+
$sheetContent = file_get_contents('zip://' . $filePath . '#xl/worksheets/' . $sheetFilename . '.xml');
374+
foreach ($strings as $string) {
375+
self::assertStringContainsString("<t>$string</t>", $sheetContent);
376+
}
377+
break;
378+
379+
case 'ods':
380+
$sheetContent = file_get_contents('zip://' . $filePath . '#content.xml');
381+
$regex = '#<table:table.+table:name="' . $sheet . '">[\s\S]*?<\/table:table>#';
382+
if (!preg_match($regex, $sheetContent, $matches)) {
383+
self::fail('Sheet ' . $sheet . ' was not found in file "' . $filePath . '".');
384+
}
385+
$sheetXmlAsString = $matches[0];
295386
foreach ($strings as $string) {
296387
self::assertStringContainsString("<text:p>$string</text:p>", $sheetXmlAsString);
297388
}

0 commit comments

Comments
 (0)