Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 35d8e61

Browse files
committed
Add context options to handle BOM
1 parent e3b513b commit 35d8e61

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
-----
66

77
* deprecated the `XmlEncoder::TYPE_CASE_ATTRIBUTES` constant, use `XmlEncoder::TYPE_CAST_ATTRIBUTES` instead
8+
* added option to output an UTF-8 BOM in CSV encoder via `CsvEncoder::OUTPUT_UTF8_BOM_KEY` context option
89

910
4.3.0
1011
-----

src/Symfony/Component/Serializer/Encoder/CsvEncoder.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Serializer\Encoder;
1313

1414
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
15+
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
1516

1617
/**
1718
* Encodes CSV data.
@@ -30,6 +31,9 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
3031
const ESCAPE_FORMULAS_KEY = 'csv_escape_formulas';
3132
const AS_COLLECTION_KEY = 'as_collection';
3233
const NO_HEADERS_KEY = 'no_headers';
34+
const OUTPUT_UTF8_BOM_KEY = 'output_utf8_bom';
35+
36+
private const UTF8_BOM = "\xEF\xBB\xBF";
3337

3438
private $formulasStartCharacters = ['=', '-', '+', '@'];
3539
private $defaultContext = [
@@ -40,6 +44,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
4044
self::HEADERS_KEY => [],
4145
self::KEY_SEPARATOR_KEY => '.',
4246
self::NO_HEADERS_KEY => false,
47+
self::OUTPUT_UTF8_BOM_KEY => false,
4348
];
4449

4550
/**
@@ -90,7 +95,7 @@ public function encode($data, $format, array $context = [])
9095
}
9196
}
9297

93-
list($delimiter, $enclosure, $escapeChar, $keySeparator, $headers, $escapeFormulas) = $this->getCsvOptions($context);
98+
list($delimiter, $enclosure, $escapeChar, $keySeparator, $headers, $escapeFormulas, $outputBom) = $this->getCsvOptions($context);
9499

95100
foreach ($data as &$value) {
96101
$flattened = [];
@@ -114,6 +119,14 @@ public function encode($data, $format, array $context = [])
114119
$value = stream_get_contents($handle);
115120
fclose($handle);
116121

122+
if ($outputBom) {
123+
if (!preg_match('//u', $value)) {
124+
throw new UnexpectedValueException('You are trying to add an UTF-8 BOM to a non UTF-8 text.');
125+
}
126+
127+
$value = self::UTF8_BOM.$value;
128+
}
129+
117130
return $value;
118131
}
119132

@@ -134,6 +147,10 @@ public function decode($data, $format, array $context = [])
134147
fwrite($handle, $data);
135148
rewind($handle);
136149

150+
if (0 === strpos($data, self::UTF8_BOM)) {
151+
fseek($handle, \strlen(self::UTF8_BOM));
152+
}
153+
137154
$headers = null;
138155
$nbHeaders = 0;
139156
$headerCount = [];
@@ -238,12 +255,13 @@ private function getCsvOptions(array $context): array
238255
$keySeparator = $context[self::KEY_SEPARATOR_KEY] ?? $this->defaultContext[self::KEY_SEPARATOR_KEY];
239256
$headers = $context[self::HEADERS_KEY] ?? $this->defaultContext[self::HEADERS_KEY];
240257
$escapeFormulas = $context[self::ESCAPE_FORMULAS_KEY] ?? $this->defaultContext[self::ESCAPE_FORMULAS_KEY];
258+
$outputBom = $context[self::OUTPUT_UTF8_BOM_KEY] ?? $this->defaultContext[self::OUTPUT_UTF8_BOM_KEY];
241259

242260
if (!\is_array($headers)) {
243261
throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, \gettype($headers)));
244262
}
245263

246-
return [$delimiter, $enclosure, $escapeChar, $keySeparator, $headers, $escapeFormulas];
264+
return [$delimiter, $enclosure, $escapeChar, $keySeparator, $headers, $escapeFormulas, $outputBom];
247265
}
248266

249267
/**

src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Serializer\Encoder\CsvEncoder;
16+
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
1617

1718
/**
1819
* @author Kévin Dunglas <[email protected]>
@@ -595,4 +596,38 @@ public function testDecodeWithoutHeader()
595596
CsvEncoder::NO_HEADERS_KEY => true,
596597
]));
597598
}
599+
600+
public function testBOMIsAddedOnDemand()
601+
{
602+
$value = ['foo' => 'hello', 'bar' => 'hey ho'];
603+
604+
$this->assertEquals("\xEF\xBB\xBF".<<<'CSV'
605+
foo,bar
606+
hello,"hey ho"
607+
608+
CSV
609+
, $this->encoder->encode($value, 'csv', [CsvEncoder::OUTPUT_UTF8_BOM_KEY => true]));
610+
}
611+
612+
public function testBOMCanNotBeAddedToNonUtf8Csv()
613+
{
614+
$value = [mb_convert_encoding('ÄÖÜ', 'ISO-8859-1', 'UTF-8')];
615+
616+
$this->expectException(UnexpectedValueException::class);
617+
$this->expectExceptionMessage('You are trying to add an UTF-8 BOM to a non UTF-8 text.');
618+
$this->encoder->encode($value, 'csv', [CsvEncoder::OUTPUT_UTF8_BOM_KEY => true]);
619+
}
620+
621+
public function testBOMIsStripped()
622+
{
623+
$csv = "\xEF\xBB\xBF".<<<'CSV'
624+
foo,bar
625+
hello,"hey ho"
626+
627+
CSV;
628+
$this->assertEquals(
629+
['foo' => 'hello', 'bar' => 'hey ho'],
630+
$this->encoder->decode($csv, 'csv', [CsvEncoder::AS_COLLECTION_KEY => false])
631+
);
632+
}
598633
}

0 commit comments

Comments
 (0)